我有我的主GUI线程,第二个线程在它自己的ApplicationContext中运行(为了保持活动,即使没有工作要做)。我想从我的GUI线程调用我的第二个线程上的方法,但是如果我只是调用thread.Method();它似乎在我的主GUI线程上运行并导致我的GUI无响应。在不同线程上调用方法的最佳方法是什么?
更新 我真正想做的是在两个线程之间进行通信,而不是与GUI通信。 GUI恰好是需要与我的第二个线程进行通信的线程之一。
更新#2: 好吧,我一定很想丢东西。我创建了一个事件和一个委托,并让我的工作线程订阅了该事件。但是当我调用Invoke(MyEvent)时;从我的GUI线程中,工作线程最终在GUI线程上工作并挂起GUI线程直到它完成处理。在没有轮询静态对象的情况下,我正在尝试做什么呢?
答案 0 :(得分:39)
无论如何,这就是我的工作。
确保不要在两个线程之间共享对象。一旦您的GUI线程在队列中粘贴消息,GUI线程就不再拥有该消息。它不能保留对该消息的引用,否则您将陷入麻烦。
这不会为您提供最佳性能,但对于大多数应用程序来说它都足够好。更重要的是,它会使犯错误更加困难。
更新:不要使用SyncLock和队列。而是使用ConcurrentQueue,它将自动为您处理任何锁定。你会获得更好的表现,也不会犯错误。
答案 1 :(得分:12)
.Net已经附带了System.ComponentModel.BackgroundWorker
类,专门用于处理执行后台任务和与GUI通信。使用它。
答案 2 :(得分:6)
实际上,你已经创建了一个穷人的ThreadPool版本。你的第二个问题就是坐在那里什么都不做,如果没有相当多的工作,你不能让它为你工作。您必须将委托传递到一个队列,然后您的线程将起飞并执行。
您最好的选择是按照您的意图行事,只需使用.NET ThreadPool即可完成工作。
答案 3 :(得分:6)
Dude,阅读Albahari的.Net线程免费电子书。 我通过任何方式连接到它,所以这不是插头。 我读过它,我的同事读了它,我已经多次使用它了。
我建议创建一个生产者/消费者类,在那里你可以启动一个等待(非阻塞)的线程,将任务排入队列,并发出信号以开始工作。
只是google for it。
答案 4 :(得分:3)
我假设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);
}
在使用此方法时,请注意在后台线程上执行回调,因此必须使用Invoke对GUI进行任何更新。
或者,您可以使用共享状态在线程之间进行通信,并使用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();
}
}
}
答案 5 :(得分:2)
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);
}
}
以下是一些测试它的示例代码:
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();
}
}
答案 6 :(得分:1)
使用同步对象来通知线程它需要处理新数据(或GUI的新状态)。一种相对简单的方法是使用事件对象。这是一个如何运作的破败:
答案 7 :(得分:1)
在第二个线程中放置一个循环,大部分时间都会休眠,但是每个[Interval]它都会唤醒并检查一个共享变量,告诉它是否运行你的方法,如果共享的boolean设置为是的,然后它运行一个方法来执行您尝试执行的任何任务...在该方法中,让方法从另一个共享变量中收集所需的数据。
在主GUI线程中,将数据放入方法参数共享变量中,然后将布尔“Run”共享变量设置为true ...
在worker方法中,记得在完成后将共享bool“run”变量重置为false,这样循环就赢了; t反复运行同一个实例......
答案 8 :(得分:1)
你可以使用事件或者像Grauenwolf所说的那样 - 一个消息提示。我将每个线程包装为一个管理单例,从那里你可以轻松实现。你甚至可以做穷人公共财产翻转。
你也可以实现状态机,而不是传递消息,每个线程可以互相监视