嵌套BackgroundWorkers:RunWorkerCompleted调用错误的线程?
我正在研究异步操作,需要调用进一步的异步任务。我试图通过使用BackgroundWorkers来保持简单,结果是一个BackgroundWorker的DoWork()回调调用一个创建第二个BackgroundWorker的方法,就像这样(减去错误检查和所有那些为了简洁起见):
class Class1
{
private BackgroundWorker _worker = null;
public void DoSomethingAsync()
{
_worker = new BackgroundWorker();
_worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_worker_RunWorkerCompleted);
_worker.DoWork += new DoWorkEventHandler(_worker_DoWork);
_worker.RunWorkerAsync();
}
void _worker_DoWork(object sender, DoWorkEventArgs e)
{
Class2 foo = new Class2();
foo.DoSomethingElseAsync();
while(foo.IsBusy) Thread.Sleep(0); // try to wait for foo to finish.
}
void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// do stuff
}
}
class Class2
{
private BackgroundWorker _worker = null;
Thread _originalThread = null;
public AsyncCompletedEventHandler DoSomethingCompleted;
public bool IsBusy { get { return _worker != null && _worker.IsBusy; } }
public void DoSomethingElseAsync()
{
_originalThread = Thread.CurrentThread;
_worker = new BackgroundWorker();
_worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_worker_RunWorkerCompleted);
_worker.DoWork += new DoWorkEventHandler(_worker_DoWork);
_worker.RunWorkerAsync();
}
void _worker_DoWork(object sender, DoWorkEventArgs e)
{
// do stuff
}
void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Debug.Assert(Thread.CurrentThread == _originalThread); // fails
// Assuming the above Assert() were excluded, the following event would be raised on the wrong thread.
if (DoSomethingCompleted != null) DoSomethingCompleted(this, new AsyncCompletedEventArgs(e.Error, e.Cancelled, null));
}
}
所以问题是,我期望 Class2._Worker_RunWorkerCompleted() 在调用 Class2.DoSomethingElseAsync() 的同一线程上执行。这种情况永远不会发生——相反,回调在一个全新的线程上运行。
我的怀疑是:Class1 的 _worker_DoWork() 永远不会返回,这意味着线程永远不会返回到事件侦听器,即使事件侦听器存在(我怀疑有一个不存在)。另一方面,如果_worker_DoWork()确实返回,Class1的BackgroundWorker将自动提前完成——它需要等待Class2完成工作才能完成其工作。
这就引出了两个问题:
- 我的怀疑正确吗?
- 像这样嵌套异步操作的最佳方法是什么?我可以挽救BackgroundWorker方法,还是有其他更合适的技术?
I'm working on asynchronous operation which needs to invoke further asynchronous tasks. I'm trying to keep it simple by using BackgroundWorkers, with the result being that one BackgroundWorker's DoWork() callback calls a method which creates a second BackgroundWorker, like so (minus error checking and all that jazz for brevity):
class Class1
{
private BackgroundWorker _worker = null;
public void DoSomethingAsync()
{
_worker = new BackgroundWorker();
_worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_worker_RunWorkerCompleted);
_worker.DoWork += new DoWorkEventHandler(_worker_DoWork);
_worker.RunWorkerAsync();
}
void _worker_DoWork(object sender, DoWorkEventArgs e)
{
Class2 foo = new Class2();
foo.DoSomethingElseAsync();
while(foo.IsBusy) Thread.Sleep(0); // try to wait for foo to finish.
}
void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// do stuff
}
}
class Class2
{
private BackgroundWorker _worker = null;
Thread _originalThread = null;
public AsyncCompletedEventHandler DoSomethingCompleted;
public bool IsBusy { get { return _worker != null && _worker.IsBusy; } }
public void DoSomethingElseAsync()
{
_originalThread = Thread.CurrentThread;
_worker = new BackgroundWorker();
_worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_worker_RunWorkerCompleted);
_worker.DoWork += new DoWorkEventHandler(_worker_DoWork);
_worker.RunWorkerAsync();
}
void _worker_DoWork(object sender, DoWorkEventArgs e)
{
// do stuff
}
void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Debug.Assert(Thread.CurrentThread == _originalThread); // fails
// Assuming the above Assert() were excluded, the following event would be raised on the wrong thread.
if (DoSomethingCompleted != null) DoSomethingCompleted(this, new AsyncCompletedEventArgs(e.Error, e.Cancelled, null));
}
}
So the problem is, I'm expecting Class2._Worker_RunWorkerCompleted() to execute on the same thread on which Class2.DoSomethingElseAsync() was called. This never happens - instead, the callback runs on a completely new thread.
Here's my suspicion: Class1's _worker_DoWork() never returns, which means that thread would never get back to an event listener, even if one existed (I suspect one doesn't). On the other hand, if _worker_DoWork() did return, Class1's BackgroundWorker would automatically finish prematurely - it needs to wait for Class2 to finish working before it can finish its work.
That leads to two questions:
- Is my suspicion correct?
- What's the best way to nest asynchronous operations like this? Can I salvage the BackgroundWorker approach, or is there some other, more suitable technique?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
如果在 UI 线程上创建
BackgroundWorker
,则DoWork
将在线程池线程上运行,而RunWorkerCompleted
将在 UI 线程上运行。如果
BackgroundWorker
在后台线程(即不是 UI 线程)上创建,DoWork
仍将在线程池线程上运行,并且RunWorkerCompleted
也将运行在线程池线程上运行。在您的情况下,由于您无法封送对任意(线程池)线程的调用,因此您将无法保证您想要的行为,尽管您可能想看看 System.Threading。同步上下文。
If a
BackgroundWorker
is created on the UI thread,DoWork
will run on a thread pool thread andRunWorkerCompleted
will run on the UI thread.If a
BackgroundWorker
is created on a background thread (ie not the UI thread)DoWork
will still run on a thread pool thread andRunWorkerCompleted
will also run on a thread pool thread.In your case, since you can't marshal a call to an arbitrary (thread pool) thread, you won't be able to guarantee the behaviour you want, although you might want to take a look at
System.Threading.SynchronizationContext
.您应该使用 ManualResetEvent 在线程之间进行通信:
http://msdn.microsoft.com/en-us/library/system.threading.manualresetevent%28VS.71%29.aspx
You should use ManualResetEvent to communicate between threads:
http://msdn.microsoft.com/en-us/library/system.threading.manualresetevent%28VS.71%29.aspx
首先,我看不到任何实际开始运行工作人员的地方。您可以更改 DoSomethingAsync 方法(还可以在 Class2 中添加对 DoSomethingElseAsync 方法的调用)
其次,工作处理程序(_worker_DoWork 方法)不能保证与对 DoSomethingAsync 的调用位于同一线程上 - 这是整个要点后台工作者。即/在另一个线程上工作。这同样适用于工作完成处理程序(_worker_RunWorkerCompleted 方法)。
最后,附加两个不同的后台工作人员似乎没有意义,除非顶级工作人员(Class1)也始终需要进行 Class2 工作。最好让一个经理来处理每个后台工作人员。
Firstly, I can't see anywhere that actually starts running the worker. You could change the DoSomethingAsync method (also add the call to the DoSomethingElseAsync method in Class2)
Secondly, the work handler (the _worker_DoWork method) is not guaranteed to be on the same thread as the call to DoSomethingAsync - this is the whole point of the Background Worker. ie/ to do work on another thread. The same applies for worker complete handler (the _worker_RunWorkerCompleted method).
Finally, It doesn't seem to make sense to attach the two different background workers unless the top level one (Class1) always requires Class2 work to happen too. You would be better of having a single manager to handle each background worker.