C# 是否可以将事件处理程序放在与调用者不同的线程上?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/486393/
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
Is it possible to put an event handler on a different thread to the caller?
提问by Rob Gray
Lets say I have a component called Tasking (that I cannot modify) which exposes a method “DoTask” that does some possibly lengthy calculations and returns the result in via an event TaskCompleted. Normally this is called in a windows form that the user closes after she gets the results.
假设我有一个名为 Tasking(我无法修改)的组件,它公开了一个方法“DoTask”,该方法执行一些可能冗长的计算并通过事件 TaskCompleted 返回结果。通常这是在用户获得结果后关闭的窗口形式中调用的。
In my particular scenario I need to associate some data (a database record) with the data returned in TaskCompleted and use that to update the database record.
在我的特定场景中,我需要将一些数据(数据库记录)与 TaskCompleted 中返回的数据相关联,并使用它来更新数据库记录。
I've investigated the use of AutoResetEvent to notify when the event is handled. The problem with that is AutoResetEvent.WaitOne() will block and the event handler will never get called. Normally AutoResetEvents is called be a separate thread, so I guess that means that the event handler is on the same thread as the method that calls.
我研究了使用 AutoResetEvent 来通知何时处理事件。问题在于 AutoResetEvent.WaitOne() 将阻塞并且事件处理程序永远不会被调用。通常 AutoResetEvents 被称为一个单独的线程,所以我猜这意味着事件处理程序与调用的方法在同一线程上。
Essentially I want to turn an asynchronous call, where the results are returned via an event, into a synchronous call (ie call DoSyncTask from another class) by blocking until the event is handled and the results placed in a location accessible to both the event handler and the method that called the method that started the async call.
本质上,我想通过阻塞直到处理事件并将结果放置在两个事件处理程序都可以访问的位置,将异步调用(结果通过事件返回)转换为同步调用(即从另一个类调用 DoSyncTask)以及调用启动异步调用的方法的方法。
public class SyncTask
{
TaskCompletedEventArgs data;
AutoResetEvent taskDone;
public SyncTask()
{
taskDone = new AutoResetEvent(false);
}
public string DoSyncTask(int latitude, int longitude)
{
Task t = new Task();
t.Completed = new TaskCompletedEventHandler(TaskCompleted);
t.DoTask(latitude, longitude);
taskDone.WaitOne(); // but something more like Application.DoEvents(); in WinForms.
taskDone.Reset();
return data.Street;
}
private void TaskCompleted(object sender, TaskCompletedEventArgs e)
{
data = e;
taskDone.Set(); //or some other mechanism to signal to DoSyncTask that the work is complete.
}
}
In a Windows App the following works correctly.
public class SyncTask
{
TaskCompletedEventArgs data;
public SyncTask()
{
taskDone = new AutoResetEvent(false);
}
public string DoSyncTask(int latitude, int longitude)
{
Task t = new Task();
t.Completed = new TaskCompletedEventHandler(TaskCompleted);
t.DoTask(latitude, longitude);
while (data == null) Application.DoEvents();
return data.Street;
}
private void TaskCompleted(object sender, TaskCompletedEventArgs e)
{
data = e;
}
}
I just need to replicate that behaviour in a window service, where Application.Run isn't called and the ApplicationContext object isn't available.
我只需要在不调用 Application.Run 且 ApplicationContext 对象不可用的窗口服务中复制该行为。
回答by Jason Down
Maybe you could get DoSyncTask to start a timer object that checks for the value of your data variable at some appropriate interval. Once data has a value, you could then have another event fire to tell you that data now has a value (and shut the timer off of course).
也许您可以让 DoSyncTask 启动一个计时器对象,该对象以某个适当的时间间隔检查数据变量的值。一旦数据具有值,您就可以触发另一个事件来告诉您数据现在具有值(当然并关闭计时器)。
Pretty ugly hack, but it could work... in theory.
相当丑陋的黑客,但它可以工作......理论上。
Sorry, that's the best I can come up with half asleep. Time for bed...
抱歉,这是我半睡半醒能想到的最好办法。该睡了...
回答by MichaelGG
If Task is a WinForms component, it might be very aware of threading issues and Invoke the event handler on the main thread -- which seems to be what you're seeing.
如果 Task 是 WinForms 组件,它可能非常了解线程问题并在主线程上调用事件处理程序——这似乎就是您所看到的。
So, it might be that it relies on a message pump happening or something. Application.Run has overloads that are for non-GUI apps. You might consider getting a thread to startup and pump to see if that fixes the issue.
因此,它可能依赖于发生的消息泵或其他事情。Application.Run 具有用于非 GUI 应用程序的重载。您可能会考虑让一个线程启动和抽水,看看是否能解决问题。
I'd also recommend using Reflector to get a look at the source code of the component to figure out what it's doing.
我还建议使用 Reflector 查看组件的源代码以了解它在做什么。
回答by Scott Weinstein
You've almost got it. You need the DoTask method to run on a different thread so the WaitOne call won't prevent work from being done. Something like this:
你已经差不多了。您需要 DoTask 方法在不同的线程上运行,以便 WaitOne 调用不会阻止工作完成。像这样的东西:
Action<int, int> doTaskAction = t.DoTask;
doTaskAction.BeginInvoke(latitude, longitude, cb => doTaskAction.EndInvoke(cb), null);
taskDone.WaitOne();
回答by Rob Gray
I worked out a solution to the async to sync problem, at least using all .NET classes.
我找到了异步同步问题的解决方案,至少使用了所有 .NET 类。
It still doesn't work with COM. I suspect because of STA threading. The Event raised by the .NET component that hosts the COM OCX is never handled by my worker thread, so I get a deadlock on WaitOne().
它仍然不适用于 COM。我怀疑是因为 STA 线程。由托管 COM OCX 的 .NET 组件引发的事件从未由我的工作线程处理,因此我在 WaitOne() 上遇到了死锁。
someone else may appreciate the solution though :)
不过,其他人可能会欣赏解决方案:)
回答by dviljoen
My comment on Scott W's answer seems a little cryptic after I re-read it. So let me be more explicit:
在我重新阅读之后,我对 Scott W 的回答的评论似乎有点神秘。所以让我更明确地说:
while( !done )
{
taskDone.WaitOne( 200 );
Application.DoEvents();
}
The WaitOne( 200 ) will cause it to return control to your UI thread 5 times per second (you can adjust this as you wish). The DoEvents() call will flush the windows event queue (the one that handles all windows event handling like painting, etc.). Add two members to your class (one bool flag "done" in this example, and one return data "street" in your example).
WaitOne( 200 ) 将导致它每秒 5 次将控制权返回给您的 UI 线程(您可以根据需要进行调整)。DoEvents() 调用将刷新 windows 事件队列(处理所有 windows 事件处理如绘画等的队列)。将两个成员添加到您的类中(在此示例中一个 bool 标志“done”,在您的示例中一个返回数据“street”)。
That is the simplest way to get what you want done. (I have very similar code in an app of my own, so I know it works)
这是完成您想做的事情的最简单方法。(我在自己的应用程序中有非常相似的代码,所以我知道它有效)
回答by Sorskoot
I've had some trouble lately with making asynchronous calls and events at threads and returning them to the main thread.
我最近在线程上进行异步调用和事件并将它们返回到主线程时遇到了一些麻烦。
I used SynchronizationContextto keep track of things. The (pseudo)code below shows what is working for me at the moment.
我使用SynchronizationContext来跟踪事物。下面的(伪)代码显示了目前对我有用的内容。
SynchronizationContext context;
void start()
{
//First store the current context
//to call back to it later
context = SynchronizationContext.Current;
//Start a thread and make it call
//the async method, for example:
Proxy.BeginCodeLookup(aVariable,
new AsyncCallback(LookupResult),
AsyncState);
//Now continue with what you were doing
//and let the lookup finish
}
void LookupResult(IAsyncResult result)
{
//when the async function is finished
//this method is called. It's on
//the same thread as the the caller,
//BeginCodeLookup in this case.
result.AsyncWaitHandle.WaitOne();
var LookupResult= Proxy.EndCodeLookup(result);
//The SynchronizationContext.Send method
//performs a callback to the thread of the
//context, in this case the main thread
context.Send(new SendOrPostCallback(OnLookupCompleted),
result.AsyncState);
}
void OnLookupCompleted(object state)
{
//now this code will be executed on the
//main thread.
}
I hope this helps, as it fixed the problem for me.
我希望这会有所帮助,因为它为我解决了问题。
回答by Jaider
Your code is almost right... I just changed
你的代码几乎是正确的......我只是改变了
t.DoTask(latitude, longitude);
for
为了
new Thread(() => t.DoTask(latitude, longitude)).Start();
TaskCompleted
will be executed in the same thread as DoTask
does. This should work.
TaskCompleted
将在同一个线程中DoTask
执行。这应该有效。