10

假设我有一个名为 Tasking 的组件(我无法修改),它公开了一个方法“DoTask”,该方法执行一些可能冗长的计算并通过事件 TaskCompleted 返回结果。通常这是在用户获得结果后关闭的窗口形式中调用的。

在我的特定场景中,我需要将一些数据(数据库记录)与 TaskCompleted 返回的数据相关联,并使用它来更新数据库记录。

我调查了使用 AutoResetEvent 来通知事件何时被处理。问题是 AutoResetEvent.WaitOne() 将阻塞并且永远不会调用事件处理程序。通常 AutoResetEvents 被称为是一个单独的线程,所以我猜这意味着事件处理程序与调用的方法位于同一线程上。

本质上,我想通过阻塞将异步调用(结果通过事件返回)转换为同步调用(即从另一个类调用 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;
}
}

我只需要在不调用 Application.Run 且 ApplicationContext 对象不可用的窗口服务中复制该行为。

4

7 回答 7

3

我最近在线程上进行异步调用和事件并将它们返回到主线程时遇到了一些麻烦。

我使用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.
}

我希望这会有所帮助,因为它为我解决了问题。

于 2009-02-04T10:13:44.350 回答
2

也许您可以让 DoSyncTask 启动一个计时器对象,以某个适当的时间间隔检查数据变量的值。一旦数据有一个值,您就可以触发另一个事件来告诉您数据现在有一个值(当然还要关闭计时器)。

相当丑陋的黑客,但它可以工作......理论上。

抱歉,这是我能想到的最好的半睡半醒。该睡了...

于 2009-01-28T05:00:51.603 回答
2

我为异步同步问题制定了一个解决方案,至少使用所有 .NET 类。

http://geekswithblogs.net/rgray/archive/2009/01/29/turning-an-asynchronous-call-into-a-synchronous-call.aspx

它仍然不适用于 COM。我怀疑是因为 STA 线程。由承载 COM OCX 的 .NET 组件引发的事件从未由我的工作线程处理,因此我在 WaitOne() 上遇到了死锁。

不过,其他人可能会欣赏该解决方案:)

于 2009-01-29T21:54:27.590 回答
0

在我重新阅读之后,我对 Scott W 答案的评论似乎有点神秘。所以让我更明确一点:

while( !done )
{
    taskDone.WaitOne( 200 );
    Application.DoEvents();
}

WaitOne( 200 ) 将使其每秒将控制权返回给您的 UI 线程 5 次(您可以根据需要进行调整)。DoEvents() 调用将刷新 windows 事件队列(处理所有 windows 事件处理的队列,如绘画等)。向您的班级添加两个成员(在本示例中一个布尔标志“完成”,在您的示例中一个返回数据“street”)。

这是完成你想做的最简单的方法。(我自己的应用程序中有非常相似的代码,所以我知道它有效)

于 2009-02-03T00:04:29.493 回答
0

如果 Task 是一个 WinForms 组件,它可能会非常了解线程问题并在主线程上调用事件处理程序——这似乎就是您所看到的。

因此,它可能依赖于消息泵的发生或其他事情。Application.Run 具有适用于非 GUI 应用程序的重载。您可能会考虑让一个线程启动和抽水,看看是否能解决问题。

我还建议使用 Reflector 查看组件的源代码,以了解它在做什么。

于 2009-01-28T05:01:46.487 回答
0

你的代码几乎是正确的......我刚刚改变了

t.DoTask(latitude, longitude);

为了

new Thread(() => t.DoTask(latitude, longitude)).Start();

TaskCompleted将在同一个线程中DoTask执行。这应该有效。

于 2013-01-09T20:26:38.330 回答
0

你几乎明白了。您需要 DoTask 方法在不同的线程上运行,以便 WaitOne 调用不会阻止工作的完成。像这样的东西:

Action<int, int> doTaskAction = t.DoTask;
doTaskAction.BeginInvoke(latitude, longitude, cb => doTaskAction.EndInvoke(cb), null);
taskDone.WaitOne();
于 2009-01-28T05:22:20.233 回答