代码之家  ›  专栏  ›  技术社区  ›  Rob Gray

是否可以将事件处理程序放在调用方的不同线程上?

  •  9
  • Rob Gray  · 技术社区  · 15 年前

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

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

    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对象不可用。

    7 回复  |  直到 15 年前
        1
  •  3
  •   Sorskoot    15 年前

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

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

        2
  •  2
  •   Jason Down    15 年前

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

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

        3
  •  2
  •   Rob Gray    15 年前

    我解决了异步同步问题,至少使用了所有.NET类。

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

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

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

        4
  •  0
  •   MichaelGG    15 年前

    所以,这可能是因为它依赖于一个消息泵或其他东西的发生。Application.Run有用于非GUI应用程序的重载。你可以考虑让一个线程启动和泵,看看是否可以解决这个问题。

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

        5
  •  0
  •   Scott Weinstein    15 年前

    Action<int, int> doTaskAction = t.DoTask;
    doTaskAction.BeginInvoke(latitude, longitude, cb => doTaskAction.EndInvoke(cb), null);
    taskDone.WaitOne();
    
        6
  •  0
  •   dviljoen    15 年前

    在我重读斯科特W的答案后,我对它的评论似乎有点晦涩。所以让我更明确地说:

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

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

        7
  •  0
  •   Jaider    11 年前

    你的代码几乎是正确的。。。我刚换了衣服

    t.DoTask(latitude, longitude);
    

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

    TaskCompleted 将在与 DoTask