.NET:如何在BeginInvoke回调期间与表单进行通信?

时间:2009-02-03 18:45:58

标签: .net asynchronous delegates

我有一个长时间运行的功能¹:

public string FindPasswordFromHash(String hash)
{
    ...
}

被称为:

private void Button1_Click(object sender, EventArgs e)
{
    PasswordTextBox.Text = FindPasswordFromHash(HashTextBox.Text);
}

现在我想将它转换为异步的BeginInvoke / EndInvoke委托模式:

private void Button1_Click(object sender, EventArgs e)
{
   MyAsyncDelegate asyncDelegate = new MyAsyncDelegate(HashTextBox.Text);
   asyncDelegte.BeginInvoke(hash, CompleteCallback, null);
}

private void CompleteCallback(IAsyncResult ar)
{
   MyAsyncDelegate asyncDelegate = ((AsyncResult)ar).AsyncDelegate;
   PasswordTextBox.Text = asyncDelegate.EndInvoke(asyncResult);
}

delegate string MyAsyncDelegate(String hash);

当然,这不起作用,因为异步委托的实现方式的漏洞抽象:

  

“跨线程操作无效:   控制'PasswordTextBox'访问   从线程以外的线程来看它   创建于。“

鉴于异步委托模式是为了将长时间运行的操作转换为异步操作而发明的 - 使用BeginInvoke / EndInvoke替换同步调用的正确技巧是什么?

更具体地说,强制回调被编组回调用线程的方法是什么?


¹例如发明了功能名称

3 个答案:

答案 0 :(得分:4)

您正在正确处理BeginInvoke()和EndInvoke()调用。您只需要处理需要在GUI线程上完成操作GUI的事实。

幸运的是,框架提供了Control.Invoke()方法,允许您在GUI线程上执行代码。

我通常做这样的事情:

private void SetPasswordText(string password){
  if(InvokeRequired){
    MethodInvoker mi = () => SetPasswordText(password);
    Invoke(mi);
    return;
  }
  PasswordTextBox.Text = password;
}

对于这种特殊情况,你也可以这样做

private void RecoveryCompleteCallback(IAsyncResult ar)
{
   MyAsyncDelegate asyncDelegate = ((AsyncResult)ar).AsyncDelegate;
   string password = asyncDelegate.EndInvoke(asyncResult);
   Invoke(()=>{PasswordTextBox.Text = password;});
}

如果你使用C#2.0,你会这样做:

MethodInvoker mi = delegate(){ SetPasswordText(password); };
Invoke(mi);

Invoke(delegate(){PasswordTextBox.Text = password;});

答案 1 :(得分:3)

对于这样的事情,最好的选择是使用BackgroundWorker对象。后台工作程序对象将允许您运行任务并使用ReportProgress更新表单。

希望这有帮助! JFV

答案 2 :(得分:1)

这是另一个可能有助于解释幕后发生的事情的实现。

        string hash = " your hash text ";

    delegate string MyAsyncDelegate(String hash);

    delegate void UpdateDelegate(string pwd);

    private string FindHash(string hs) {
        Thread.Sleep(5000);

        return "hash computed by worker Thread: " + Thread.CurrentThread.ManagedThreadId;
    }

    private void Button1_Click(object sender, EventArgs e) {
        //invoke FindHash on another thread from the threadpool.
        MessageBox.Show("Current Thread Id: " + Thread.CurrentThread.ManagedThreadId);
        MyAsyncDelegate asyncDelegate = new MyAsyncDelegate(this.FindHash);
        asyncDelegate.BeginInvoke(hash, RecoveryCompleteCallback, asyncDelegate);
    }

    private void RecoveryCompleteCallback(IAsyncResult result) {
        MyAsyncDelegate asyncDelegate = (MyAsyncDelegate)result.AsyncState;
        string pwd = asyncDelegate.EndInvoke(result);

        UpdatePassword(pwd);
    }

    private void UpdatePassword(string s) {

        System.ComponentModel.ISynchronizeInvoke invoker = PasswordTextBox as System.ComponentModel.ISynchronizeInvoke;
        if (invoker != null && invoker.InvokeRequired) {
            // still in worker thread.
            invoker.Invoke(new UpdateDelegate(UpdatePassword), new object[] { s });
        } else {
            PasswordTextBox.Text = s;
            MessageBox.Show("Current Thread Id: " + Thread.CurrentThread.ManagedThreadId);
        }
    }

我知道它有点长,但它可以帮助你理解......基本上你永远不想从GUI线程以外的线程更新GUI。