views:

826

answers:

3

i have a long running function¹:

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

which is called like:

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

Now i want to convert it into the asynchronous BeginInvoke/EndInvoke delegate pattern:

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

Of course this doesn't work because of a leaky abstraction of the way asynchronous delegates are implemented:

"Cross-thread operation not valid: Control 'PasswordTextBox' accessed from a thread other than the thread it was created on."

Given that the asynchronous delegate pattern was invented to convert long running operations into asynchronous operations - what is the proper technique to use BeginInvoke/EndInvoke as replacement for the synchronous call?

And more specifically, what is the method to force the callback to marshalled back to the calling thread?


¹ Function name invented for example

+2  A: 

Your best bet for something like this is to use the BackgroundWorker object. The background worker object will allow you to run a task and update the form with ReportProgress.

Hope this helps! JFV

JFV
That's probably better advice than ever using BeginInvoke/EndInvoke. It's difficult to imagine an actical practical use for async delegates given the working limitations.
Ian Boyd
+3  A: 

You are handling the BeginInvoke() and EndInvoke() calls correctly. You just need to handle the fact that manipulating the GUI needs to be done on the GUI-thread.

Luckily the framework provides the Control.Invoke() method, which allows you to have code executed on the GUI thread.

I usually do something like this:

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

For this particular case, you could also just do

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

If you using C# 2.0, you would do:

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

or

Invoke(delegate(){PasswordTextBox.Text = password;});
Rasmus Faber
i forgot about the 'InvokeRequired' and using .Invoke(). But what is all that syntax? Is it C#, or is it VB? It's doesn't compile in VS2005.
Ian Boyd
It is C# 3.0. Here it is just used as shorthand for anonymous delegates.
Rasmus Faber
+1  A: 

Here is yet another implementation that might help explain what is going on under the hood.

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

I understand that it is a bit more long winded but it may help you in your understand... Essentially you never want to update the GUI from a thread other than the GUI Thread.

sbeskur