如何调用异步回调中的事件处理程序,以便在调用线程中运行

如何调用异步回调中的事件处理程序,以便在调用线程中运行

问题描述:

我正在开发一个由不同应用程序使用的VS项目/解决方案。我的工作是重构项目,并将其从使用xxxAsync方法改为使用BeginInvoke。

I am working on a VS project/solution that is used by different applications. My job is to refactor the project and change it from using xxxAsync method to using BeginInvoke.

我想出了类似于下面的代码:

I came up to something similar to the following code:

public class AsyncTestModel {
    private delegate string DoTaskDelegate();

    public static EventHandler<TaskCompletedEventArgs> OnTaskCompleted;

    public static void InvokeTask() {
        DoTaskDelegate taskDelegate = Task;
        taskDelegate.BeginInvoke(new AsyncCallback(TaskCallback), null);
    }

    private static string Task() {
        Thread.Sleep(5000);
        return "Thread Task successfully completed.";
    }

    private static void TaskCallback(IAsyncResult ar) {
        string result = ((DoTaskDelegate)((System.Runtime.Remoting.Messaging.AsyncResult)ar).AsyncDelegate).EndInvoke(ar);
        if (OnTaskCompleted != null) {
            OnTaskCompleted(null, new TaskCompletedEventArgs(result));
        }
    }
}

public class TaskCompletedEventArgs : EventArgs {
    private string _message;

    public TaskCompletedEventArgs(string message) : base() {
        _message = message;
    }

    public string Message {
        get {
            return _message;
        }
    }
}

我创建的一个新的UI项目。 UI项目包含一个按钮和一个标签控件。 UI有以下代码:

I've tested this on a new UI project I've created. The UI project contains a button and a label controls. The UI has the following code:

    private void button1_Click(object sender, EventArgs e) {
        AsyncTestModel.OnTaskCompleted += OnTaskCompleted;
        AsyncTestModel.InvokeTask();
    }

    private void OnTaskCompleted(object sender, TaskCompletedEventArgs e) {
        UpdateLabel(e.Message);
    }

    private void UpdateLabel(string message) {
        this.label1.Text = message;
    }

运行此操作后,我遇到了跨线程异常,

After running this, I've encountered the cross-thread exception saying the the control 'label1' is being accessed from other thread aside the thread that it was created.

有一种方法让我在同一个线程上调用OnTaskCompleted事件处理程序调用BeginInvoke方法?我知道我可以使用表单的InvokeRequired,并像下面这样调用窗体的BeginInvoke:

Is there a way for me to invoke the OnTaskCompleted event handler on the same thread that calls the BeginInvoke method? I know I could just use the form's InvokeRequired and call the form's BeginInvoke like the following:

    private delegate void DoUpdateLabelDelegate(string message);

    private void UpdateLabel(string message) {
        if (this.InvokeRequired) {
            IAsyncResult ar = this.BeginInvoke(new DoUpdateLabelDelegate(UpdateLabel), message);
            this.EndInvoke(ar);
            return;
        }
        this.label1.Text = message;
    }

但是上面的解决方案会要求我提出问题,开发团队处理使用我的项目/解决方案的应用程序。这些其他开发人员不应该被要求知道挂钩到事件处理程序的方法是从不同的线程运行的。

But the solution above will require me to ask and apply that solution to the other development team handling applications that uses my project/solution. Those other developers shouldn't be required to know that the methods hooked to the event handler are running from different thread.

先感谢,

按照设计,你绝对不知道哪个线程是客户端UI运行的线程。

As designed, no, you have absolutely no idea which thread is the one on which the client's UI runs.

您可以任意地要求从该UI线程调用InvokeTask()。现在你知道,你可以在InvokeTask()方法中复制SynchronizationContext.Current,然后调用它的Post()或Send()方法来调用一个触发事件的方法。这是由例如BackgroundWorker和async / await使用的模式。请注意,复制当前属性是必需的,以使此工作,不要跳过。

You can arbitrarily demand that your InvokeTask() is to be called from that UI thread. Now you know, you can copy SynchronizationContext.Current in the InvokeTask() method and, later, call its Post() or Send() method to call a method that fires the event. This is the pattern used by, for example, BackgroundWorker and async/await. Do note that copying the Current property is required to make this work, don't skip it.

当然,当你的InvokeTask()方法从UI线程调用的不是,您将看到Synchronization.Current为null,没有希望对调用进行调度。如果这是一个关注,那么你可以公开一个类型为ISynchronizeInvoke的属性,称为SynchronizingObject。现在由客户端代码进行调用,他们将没有设置属性的麻烦,他们将简单地在其表单类构造函数中分配 。并使用属性的Post或Send方法来调用引发事件的方法。这是例如Process和FileSystemWatcher类使用的模式。不要使用它,如果你希望你的库被非Winforms客户端应用程序使用,不幸的是后来的GUI库,如WPF和Silverlight不实现该接口。否则,与调用Control.Begin / Invoke()自己的方法完全相同的问题。

That of course still won't work when your InvokeTask() method is not called from the UI thread, you'll see that Synchronization.Current is null and have no hope to marshal the call. If that's a concern then you could expose a property of type ISynchronizeInvoke, call it SynchronizingObject. Now it is up to the client code to make the call, they'll have no trouble setting the property, they'll simply assign this in their form class constructor. And you use the property's Post or Send method to call the method that raises the event. This is the pattern used by for example the Process and FileSystemWatcher classes. Don't use it if you expect your library to be used by non-Winforms client apps, unfortunately later GUI libraries like WPF and Silverlight don't implement the interface. Otherwise the exact same problem with approaches like calling Control.Begin/Invoke() yourself.