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

时间:2013-11-18 11:27:12

标签: multithreading asynchronous callback delegates c#-3.0

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

我遇到了类似于以下代码的内容:

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具有以下代码:

    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;
    }

运行之后,我遇到了跨线程异常,说控制'label1'是从其创建的线程旁边的其他线程访问的。

有没有办法在调用BeginInvoke方法的同一个线程上调用OnTaskCompleted事件处理程序?我知道我可以使用表单的InvokeRequired并调用表单的BeginInvoke,如下所示:

    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;
    }

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

提前谢谢。

2 个答案:

答案 0 :(得分:1)

按照设计,不,您完全不知道哪个线程是客户端UI运行的线程。

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

当你的InvokeTask()方法从UI线程调用而不是时,当然仍然无法工作,你会发现Synchronization.Current为null并且没有希望编组调用。如果这是一个问题,那么你可以公开一个ISynchronizeInvoke类型的属性,称之为SynchronizingObject。现在由客户端代码来进行调用,他们在设置属性时没有任何问题,他们只需在其表单类构造函数中分配 this 即可。并且您使用属性的Post或Send方法来调用引发事件的方法。这是例如Process和FileSystemWatcher类使用的模式。如果您希望非Winforms客户端应用程序使用您的库,请不要使用它,不幸的是,稍后的GUI库(如WPF和Silverlight)不会实现该接口。否则与自己调用Control.Begin / Invoke()等方法完全相同。

答案 1 :(得分:0)

尝试使用它,也许它可以帮助你。

Deployment.Current.Dispatcher.BeginInvoke(() =>
        {
           //Do something...
        });