WPF文件并发上传到远程服务器

问题描述:

我是WPF和C#线程编程新手。我有一个C#WPF应用程序,需要同时将本地大文件上传到远程服务器(Windows操作系统)(线程)。请指教。



我有一些问题:



1.对于C#中的多线程,我应该选择await / async / Task(C#版本5)或BackgroundWorkder方法?哪种方法最好?



2.我应该使用哪个WPF小部件/控件上传UI和功能?



提前谢谢。

I am a WPF and C# thread programming newbie. I have a C# WPF application that requires to upload local big files to remote server (Windows OS) concurrently (thread). Please advise.

I have some questions:

1. For multiple threading in C#, I should choose await/async/Task (C# version 5) or BackgroundWorkder approach? Which approach is the best?

2. Which WPF widget(s)/control(s) I should use for uploading UI and function?

Thank you in advance.

是的,您需要使用多个线程,但可能不会太多。这取决于许多因素。如果有许多连接速度足够慢,但服务器不是瓶颈,那么即使使用更多线程来计算系统中的CPU *核心数,也总是会提高吞吐量。这是因为每个线程花费时间处于等待状态,然后不会过度使用CPU时间。另一方面,假设所有线程都使用与同一服务器的连接,这本身很慢,因此它会带来瓶颈,而不是介于两者之间的流量。在这种情况下,您只需向服务器和系统添加一些负载,而不是提高并行度。最后,想象一下最简单的情况,当你简单地并行运行多个线程时。如果您创建更多线程然后创建核心,您实际上并不真正并行工作,因为线程开始共享相同的资源。在做出最终决定之前,您需要分析所有这些情景;试验也会有所帮助,但准确的时间需要深入了解它。



现在,我会拒绝 BackgroundWorker ;这是一个很好的解决方案。通过构造函数创建线程也不是很好,因为创建的开销不必要;线程池更好但线程数量有限。我通常建议一种非常不同的方式。创建一些固定数量的线程(它可以更改,但通过读取一些配置参数,此后不会在其余的生命周期内更改)。然后,您可以在上传开始时重用这些线程,使用 EventWaitHandle 将每个线程保留在等待线程中。您可以从我介绍的非常方便的线程包装概念开始:

如何将ref参数传递给线程 [ ^ ],

更改线程(生产者)启动后的参数 [ ^ ],

C#中的MultiThreading [ ^ ]。



人所以,我在过去的答案中描述了重用和限制线程并控制它们的技巧:

在webservice中运行一个永远不会终止的作业/线程/进程(asp.net) [ ^ ],

使代码线程安全 [ ^ ],

暂停正在运行的主题 [ ^ ],

线程中的手动重置事件和自动重置事件 [ ^ ]。



现在,将你使用await / async / TPL(任务)?这是一个有争议的话题。这些技术是新的,非常先进的。它们在很多情况下都很好,但是当涉及到你的案例,网络通信和其他类似的任务时,我有充分的理由赞成使用显式线程和同步(阻塞)API。为什么?简而言之,因为它使代码更加直接和可控,并且因为您不断重用相同的线程同步技术,这些技术不依赖于特定于应用程序的异步API。请查看我过去对该主题的回答:

Async等待多个Telnet服务器 [ ^ ],

TCP套接字 - 发送和接收问题 [ ^ ],

TcpListener,TcpClient,在Visual Basic 2005中Ping [ ^ ],

多线程问题? !!! [ ^ ]。



尽管如此,您可以考虑使用await / async方法,但我不建议自动完成线程控制的TPL。为什么?因为,在您的情况下,您有一些确定性的固定设置,例如:每个线程一个TCP通道。无论如何,你最终决定。



最后,你不需要任何特定于WPF的控件或其他组件,除了通常的(按钮,一些输入控件等) 。)使用非组件类在这些线程中进行所有网络通信工作要好得多,例如 System.Net.WebRequest (抽象,具体运行时类型由URL), System.Net.HttpWebRequest System.Net.FtpWebRequest ...我不知道你的服务是什么是的,所以自己决定。如果你澄清并提出一些后续问题,我也很乐意尝试回答这些问题。



唯一与UI相关的问题是同步使用UI进行线程操作。您可能需要通知UI所有任务的进度,状态和结果。但是 - 你不能从非UI线程调用与UI相关的任何东西。相反,您需要使用 Invoke System.Windows.Threading的方法。 Dispatcher (对于Forms或WPF)或 System.Windows.Forms.Control (仅限表单)。



您将在我过去的答案中找到有关其工作原理和代码示例的详细说明:

Control.Invoke()与Control.BeginInvoke() [ ^ ],

使用Treeview扫描仪和MD5的问题 [ ^ ]。



另请参阅有关线程的更多参考资料:

如何让keydown事件在不同的操作上运行vb.net中的线程 [ ^ ],

在启用禁用+多线程后控制事件未触发 [ ^ ]。



-SA
Yes, you need to use multiple threads, but perhaps not too many. It depends on a number of factors. If you have many connections which are slow enough, but the server is not a bottleneck, you always improve the throughput, even if use more threads that the number of CPUs * cores in your system. This is because each thread spends time in the wait state and then does not overuse the CPU time. From the other hand, imagine that all threads work with the connection to the same server, which is itself slow, so it presents the bottleneck, not the traffic in between. In this situation, you simply add some load to both the server and your system, not improving the degree of parallelism. Finally, imagine the simplest situation, when you simply run several threads in parallel. If you create more threads then cores, you also don't actually work really in parallel, because threads start sharing the same resource. You need to analyze all these scenarios before making final decisions; experimenting will also be helpful, but accurate timing needs deep understanding of it.

Now, I would reject BackgroundWorker; this is a good solution in a pinch. Creating a thread through its constructor is also not so good because of unwanted overhead of the creation; thread pool is better but the number of threads is limited. I usually advice a very different way. Create some fixed number of threads (it can change, but through reading some configuration parameters, not changing thereafter during the rest of the lifetime). Then you can reuse those thread when uploading starts, keeping each one in a wait thread using EventWaitHandle. You can start with the very convenient concept of a thread wrapper I introduced:
How to pass ref parameter to the thread[^],
Change parameters of thread (producer) after it is started[^],
MultiThreading in C#[^].

Also, I described the techniques of reusing and throttling the threads and controlling them in my past answers:
Running exactly one job/thread/process in webservice that will never get terminated (asp.net)[^],
Making Code Thread Safe[^],
pause running thread[^],
ManualResetEvent and AutoResetEvent in Thread[^].

Now, shall you use await/async/TPL (tasks)? This is a controversial subject. These technologies are new and very advanced. They are good in many cases, but I have serious arguments in favor of using explicit threads and synchronous (blocking) API, when it comes to your case, network communications, and other similar tasks. Why? In brief, because it makes code more straightforward and controllable, and because you keep reusing of the same thread synchronization techniques, which don't depend on application-specific asynchronous API. Please see my past answers on the topic:
Async Await Multiple Telnet Servers[^],
TCP Socket - Send and receive question[^],
TcpListener, TcpClient, Ping in Visual Basic 2005[^],
problem in multithreading ? !!![^].

Nevertheless, you can consider using await/async approach, but I don't recommend TPL, where thread control is done automatically. Why? Because, in your case, you have some deterministic fixed setting, such as: one TCP channel per thread. Anyway, you finally decide.

Finally, you don't need any WPF-specific controls or other components, except the usual one (buttons, some input controls, etc.) It's much better to do all the network communication work in those thread using non-component classes, such as System.Net.WebRequest (abstract, concrete runtime type is chosen by the URL), System.Net.HttpWebRequest, System.Net.FtpWebRequest… I don't know what your services are, so decide by yourself. If you clarify that and ask some follow-up questions, I'll gladly try to answer them, too.

The only UI-related issue is the synchronization of the thread operation with UI. You may need to notify UI of the progress, status, results of all the tasks. But — уou cannot call anything related to UI from non-UI thread. Instead, you need to use the method Invoke or BeginInvoke of System.Windows.Threading.Dispatcher (for both Forms or WPF) or System.Windows.Forms.Control (Forms only).

You will find detailed explanation of how it works and code samples in my past answers:
Control.Invoke() vs. Control.BeginInvoke()[^],
Problem with Treeview Scanner And MD5[^].

See also more references on threading:
How to get a keydown event to operate on a different thread in vb.net[^],
Control events not firing after enable disable + multithreading[^].

—SA