在 C# 中,在 2 个线程之间传递数据的推荐方式是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/334860/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
In C# what is the recommended way of passing data between 2 threads?
提问by Jon Tackabury
I have my main GUI thread, and a second thread running inside it's own ApplicationContext (to keep it alive, even when there is no work to be done). I want to call a method on my 2nd thread from my GUI thread, but if I just call thread.Method(); it seems to be running on my main GUI thread and causes my GUI to become unresponsive. What is the best way to call methods on different threads?
我有我的主 GUI 线程,还有一个在它自己的 ApplicationContext 中运行的第二个线程(即使没有工作要做,也能保持活动状态)。我想从我的 GUI 线程调用我的第二个线程上的方法,但如果我只是调用 thread.Method(); 它似乎在我的主 GUI 线程上运行并导致我的 GUI 无响应。在不同线程上调用方法的最佳方法是什么?
Update:What I'm really looking to do here is communicate between 2 threads, not communicate with a GUI. The GUI just happens to be one of the threads that will need to communicate with my 2nd thread.
更新:我真正想做的是在 2 个线程之间进行通信,而不是与 GUI 进行通信。GUI 恰好是需要与我的第二个线程通信的线程之一。
Update #2:Ok, I must really be missing something. I created an event and a delegate and had my worker thread subscribe to the event. But when I call Invoke(MyEvent); from my GUI thread the work that the worker thread does ends up being on the GUI thread and hangs the GUI thread until it's done processing. Is what I'm trying to do even possible, without polling on a static object?
更新 #2:好的,我一定是真的遗漏了什么。我创建了一个事件和一个委托,并让我的工作线程订阅了该事件。但是当我调用 Invoke(MyEvent); 从我的 GUI 线程,工作线程所做的工作最终在 GUI 线程上并挂起 GUI 线程,直到它完成处理。在不轮询静态对象的情况下,我正在尝试做的事情是否可能?
采纳答案by Dour High Arch
.Net already comes with a System.ComponentModel.BackgroundWorker
class specifically to handle performing background tasks and communicating with a GUI. Use it.
.Net 已经带有一个System.ComponentModel.BackgroundWorker
专门用于处理后台任务和与 GUI 通信的类。用它。
回答by Rob Prouse
In effect, you have created a poor man's version of a ThreadPool. Your second thread is just sitting there doing nothing and without a fair amount of work on your part, you can't just get it to do work for you. You would have to pass delegates into a queue that your thread then takes off and executes.
实际上,您创建了一个穷人版的 ThreadPool。你的第二个线程只是坐在那里什么也不做,如果你没有做大量的工作,你就不能让它为你工作。您必须将委托传递到一个队列中,然后您的线程会启动并执行该队列。
Your best bet is to do what you intended and just use the .NET ThreadPool and give it work to do.
最好的办法是做你想做的事,只使用 .NET ThreadPool 并让它工作。
回答by Brian
Use a synchronization object to signal the thread that it needs to process the new data (or the GUI's new state). One relatively simple way to do this is to use an event object. Here's a run-down of how that would work:
使用同步对象通知线程它需要处理新数据(或 GUI 的新状态)。一种相对简单的方法是使用事件对象。这是如何工作的简要说明:
- GUI thread an 2nd thread share an event object (so they both know about it)
- 2nd thread typically runs in a loop of some sort, and each time it waits for the event to be signaled
- GUI thread signals the event when it needs the 2nd thread to do something
- When the 2nd thread is done, it resets the event and waits again (or exits)
- GUI 线程和第二个线程共享一个事件对象(所以他们都知道)
- 第二个线程通常在某种循环中运行,并且每次等待事件被发出信号
- GUI 线程在需要第二个线程做某事时发出事件信号
- 当第二个线程完成时,它重置事件并再次等待(或退出)
回答by Charles Bretana
Put a loop in your second thread, that sleeps most of the time, but every [Interval] it wakes up and checks a shared variable that tells it whether to run your method or not, and if that shared boolean is set to true, then it runs a method that performs whatever task you are trying to perform... In that method, have the method gather the data required from another shared variable.
在您的第二个线程中放置一个循环,该循环大部分时间都在休眠,但它每 [Interval] 唤醒并检查一个共享变量,该变量告诉它是否运行您的方法,如果该共享布尔值设置为 true,则它运行一个方法来执行您尝试执行的任何任务...在该方法中,让该方法从另一个共享变量收集所需的数据。
In main GUI thread, put the data into the method parameter shared variable, then set the boolean "Run" shared variable to true...
在主 GUI 线程中,将数据放入方法参数共享变量中,然后将布尔值“Run”共享变量设置为 true...
Inside the worker method, remember to reset the shared bool "run" variable to false when you're done, so the loop won;t run the same instance over and over...
在工作方法中,记住在完成后将共享的 bool“run”变量重置为 false,这样循环就不会一遍又一遍地运行相同的实例......
回答by Lee
I'm assuming some event in the GUI requires some long-running task to start which be run in the background - there are two main ways to do this. If you simply want to call a method on a different thread then you can do it by Calling Synchronous Methods Asynchronously. I usually do something like this:
我假设 GUI 中的某些事件需要一些长时间运行的任务才能启动,这些任务在后台运行 - 有两种主要方法可以做到这一点。如果您只是想在不同的线程上调用一个方法,那么您可以通过Calling Synchronous Methods Asynchronously 来实现。我通常做这样的事情:
//delegate with same prototype as the method to call asynchrously
delegate void ProcessItemDelegate(object item);
//method to call asynchronously
private void ProcessItem(object item) { ... }
//method in the GUI thread
private void DoWork(object itemToProcess)
{
//create delegate to call asynchronously...
ProcessItemDelegate d = new ProcessItemDelegate(this.ProcessItem);
IAsyncResult result = d.BeginInvoke(itemToProcess,
new AsyncCallback(this.CallBackMethod),
d);
}
//method called when the async operation has completed
private void CallbackMethod(IAsyncResult ar)
{
ProcessItemDelegate d = (ProcessItemDelegate)ar.AsyncState;
//EndInvoke must be called on any delegate called asynchronously!
d.EndInvoke(ar);
}
Be aware when using this method that the callback is executed on the background thread, so any updates to the GUI must be done using Invoke.
使用此方法时请注意回调是在后台线程上执行的,因此必须使用 Invoke 完成对 GUI 的任何更新。
Alternatively you could use shared state to communicate between threads and use an EventWaitHandleto signal updates to the shared state - in this example a method in the GUI adds work items to a queue to be handled in the background. The worker thread processes items from the queue when work becomes available.
或者,您可以使用共享状态在线程之间进行通信,并使用EventWaitHandle来通知共享状态的更新 - 在此示例中,GUI 中的方法将工作项添加到要在后台处理的队列中。当工作可用时,工作线程处理队列中的项目。
//shared state
private Queue workQueue;
private EventWaitHandle eventHandle;
//method running in gui thread
private void DoWork(Item itemToProcess)
{
//use a private lock object instead of lock...
lock(this.workQueue)
{
this.workQueue.Add(itemToProcess);
this.eventHandle.Set();
}
}
//method that runs on the background thread
private void QueueMonitor()
{
while(keepRunning)
{
//if the event handle is not signalled the processing thread will sleep here until it is signalled or the timeout expires
if(this.eventHandle.WaitOne(optionalTimeout))
{
lock(this.workQueue)
{
while(this.workQueue.Count > 0)
{
Item itemToProcess = this.workQueue.Dequeue();
//do something with item...
}
}
//reset wait handle - note that AutoResetEvent resets automatically
this.eventHandle.Reset();
}
}
}
回答by Hans Passant
The convenience of Control.BeginInvoke() is hard to pass up. You don't have to. Add a new class to your project and paste this code:
Control.BeginInvoke() 的便利性不容忽视。你不必。向您的项目添加一个新类并粘贴以下代码:
using System;
using System.Threading;
using System.Windows.Forms;
public partial class frmWorker : Form {
public frmWorker() {
// Start the worker thread
Thread t = new Thread(new ParameterizedThreadStart(WorkerThread));
t.IsBackground = true;
t.Start(this);
}
public void Stop() {
// Synchronous thread stop
this.Invoke(new MethodInvoker(stopWorker), null);
}
private void stopWorker() {
this.Close();
}
private static void WorkerThread(object frm) {
// Start the message loop
frmWorker f = frm as frmWorker;
f.CreateHandle();
Application.Run(f);
}
protected override void SetVisibleCore(bool value) {
// Shouldn't become visible
value = false;
base.SetVisibleCore(value);
}
}
Here's some sample code to test it:
下面是一些示例代码来测试它:
public partial class Form1 : Form {
private frmWorker mWorker;
public Form1() {
InitializeComponent();
mWorker = new frmWorker();
}
private void button1_Click(object sender, EventArgs e) {
Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);
mWorker.BeginInvoke(new MethodInvoker(RunThisOnThread));
}
private void RunThisOnThread() {
Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);
}
private void button2_Click(object sender, EventArgs e) {
mWorker.Stop();
}
}
回答by Jonathan Allen
Wow, I can't believe how may people didn't bother reading the question.
哇,我不敢相信人们怎么会不费心阅读这个问题。
Anyways, this is what I do.
无论如何,这就是我所做的。
- Create you "message" classes. This stores all the information you want to share.
- Create a Queue<T> for each thread. Use a SyncLock (C# lock) to read/write to it.
- When you want to talk to a thread, send it a message object with a copyof all the information it needs by adding the message to the queue.
- The worker thread can then read from the queue, reading and processing each message in order. When there are no messages, simply sleep.
- 为您创建“消息”类。这存储了您要共享的所有信息。
- 为每个线程创建一个 Queue<T>。使用 SyncLock(C# 锁)对其进行读/写。
- 当您想与一个线程交谈时,通过将消息添加到队列,向它发送一个消息对象,其中包含它需要的所有信息的副本。
- 然后工作线程可以从队列中读取,依次读取和处理每条消息。当没有消息时,只需睡觉。
Make sure that you don't share objects between the two threads. Once your GUI thread sticks a message in the Queue, the GUI thread no longer owns the message. It cannot hold a reference to the message, or you will get yourself into trouble.
确保您不在两个线程之间共享对象。一旦您的 GUI 线程在队列中粘贴一条消息,GUI 线程就不再拥有该消息。它不能包含对消息的引用,否则您将陷入困境。
This won't give you the best possible performance, but it will be good enough for most applications. And more importantly, it will make it much harder to make a mistake.
这不会为您提供最佳性能,但对于大多数应用程序来说已经足够了。更重要的是,它会使犯错变得更加困难。
UPDATE: Don't use a SyncLock and Queue. Instead use a ConcurrentQueue, which will handle any locking automatically for you. You'll get better performance and are less likely to make a mistake.
更新:不要使用 SyncLock 和 Queue。而是使用 ConcurrentQueue,它将自动为您处理任何锁定。您将获得更好的性能,并且不太可能出错。
回答by dfasdljkhfaskldjhfasklhf
You can use events or as Grauenwolf said - a message cue. I wrap each of my thread as a management singleton, from there you can implement either easily. You could even do poor man public properties to flip bits.
您可以使用事件或 Grauenwolf 所说的 - 消息提示。我将我的每个线程都包装为一个管理单例,从那里您可以轻松实现。你甚至可以做穷人的公共财产来翻转位。
Also you could implement a state machine, and instead of pass messages each thread could monitor each other
你也可以实现一个状态机,而不是传递消息每个线程可以相互监视
回答by small rabbit
Dude, read Albahari's .Net threading free ebook. I'm connected to it by any means, so this is no plug. I've read it, had my coworkers read it, and i've used it many times.
伙计,阅读 Albahari 的 .Net 线程免费电子书。我通过任何方式连接到它,所以这不是插头。我读过它,让我的同事也读过它,我已经用过很多次了。
I would recommend creating a producer/consumer class, where you can start a single thread that waits (non blocking), enqueue tasks to its queue, and signal it to start working.
我建议创建一个生产者/消费者类,您可以在其中启动一个等待(非阻塞)的线程,将任务排入队列,并发出信号以开始工作。
just google for it.
只是谷歌它。