.NET: Как общаться с формой во время обратного вызова BeginInvoke? - PullRequest
2 голосов
/ 03 февраля 2009

у меня есть функция длительного запуска¹:

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 ]

4 голосов
/ 03 февраля 2009

Вы правильно обрабатываете вызовы 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;});
3 голосов
/ 03 февраля 2009

Лучшая ставка для чего-то подобного - использовать объект BackgroundWorker . Фоновый рабочий объект позволит вам запустить задачу и обновить форму с помощью ReportProgress.

Надеюсь, это поможет! JFV

1 голос
/ 03 февраля 2009

Вот еще одна реализация, которая может помочь объяснить, что происходит под капотом.

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

Я понимаю, что это немного длиннее, но это может помочь вам понять ... По сути, вы никогда не хотите обновлять графический интерфейс из потока, отличного от потока графического интерфейса.

...