2010-09-04 1 views
7

Я пишу очень простой асинхронный вспомогательный класс, чтобы согласиться с моим проектом. Целью класса является то, что он позволяет использовать метод в фоновом потоке. Вот код;C# async callback все еще на фоне потока ... help! (желательно без InvokeRequired)


    internal class AsyncHelper 
    { 
     private readonly Stopwatch timer = new Stopwatch(); 
     internal event DownloadCompleteHandler OnOperationComplete; 

     internal void Start(Func func, T arg) 
     { 
      timer.Start(); 
      func.BeginInvoke(Done, func); 
     } 

     private void Done(IAsyncResult cookie) 
     { 
      timer.Stop(); 
      var target = (Func) cookie.AsyncState; 
      InvokeCompleteEventArgs(target.EndInvoke(cookie)); 
     } 

     private void InvokeCompleteEventArgs(T result) 
     { 
      var args = new EventArgs(result, null, AsyncMethod.GetEventByClass, timer.Elapsed); 
      if (OnOperationComplete != null) OnOperationComplete(null, args); 
     } 

     #region Nested type: DownloadCompleteHandler 

     internal delegate void DownloadCompleteHandler(object sender, EventArgs e); 

     #endregion 
    } 

В результате задачи затем возвращается через OnOperationComplete событие. Проблема в том, что когда событие поднимается, его все еще на фоновом потоке. То есть если я пытаюсь запустить этот код (ниже), я получаю ошибку поперечной резьбы;

txtOutput.AppendText(e.Result + Environment.NewLine);

Просьба сообщить нам какие-либо мысли.

+0

Вы используете .NET 2.0, 3.5, 4.0? И вы создаете приложение WinForms, WPF, Silverlight? –

+0

.NET 4.0, библиотека классов – 2010-09-04 17:59:39

ответ

5

Используйте класс BackgroundWorker. Он по существу делает то же самое, что и вы.

 private BackgroundWorker _worker; 

    public Form1() 
    { 
     InitializeComponent(); 
     _worker = new BackgroundWorker(); 
     _worker.DoWork += Worker_DoWork; 
     _worker.RunWorkerCompleted += Work_Completed; 
    } 

    private void Work_Completed(object sender, RunWorkerCompletedEventArgs e) 
    { 
     txtOutput.Text = e.Result.ToString(); 
    } 

    private void Worker_DoWork(object sender, DoWorkEventArgs e) 
    { 
     e.Result = "Text received from long runing operation"; 
    } 
+0

Вы также можете сообщить о ходе выполнения: _worker.WorkerReportsProgress = true; // И _worker.ProgressChanged + = Progress_Changed; –

+0

Я вроде забыл ... чтобы запустить рабочего, вы должны вызвать метод _worker.RunWorkerAsync(). –

+0

Под вашим ответом есть небольшая ссылка «Изменить». :) – Timwi

0

Вы должны вызвать ваше событие в потоке пользовательского интерфейса,

WinForms

Form1.BeginInvoke(...);

WPF

Dispatcher.BeginInvoke(...);

+0

Вы предлагаете это ?; OnOperationComplete.BeginInvoke 2010-09-04 17:02:38

+0

'ISynchronizeInvoke' является устаревшим интерфейсом и не должен использоваться. –

+1

@ Джон, нет, он предлагает вам вызвать метод BeginInvoke или Invoke, обнаруженный на визуальном контроле. Таким образом вы будете маршировать вызов в поток графического интерфейса, и это позволит вам манипулировать состоянием GUI. 'Delegate.BeginInvoke' и' Control.BeginInvoke' - это не одно и то же. –

0

Используйте класс BackgroundWorker, вы в основном реализовав его Вот.

0

Если вы не строите свой класс помощи, чтобы сделать переключение контекста внутри вы всегда должны вызывать в обработчик события, потому что в вашем коде выше вы поднимаете событие на не-UI потока.

Для этого ваш помощник должен знать, как вернуться на нить ui. Вы можете передать ISynchronizeInvoke помощнику, а затем использовать его, когда это будет сделано. Somwthing как:

ISynchronizeInvoke _sync; 
internal void Start(Func func, T arg, ISynchronizeInvoke sync) 
{ 
    timer.Start(); 
    func.BeginInvoke(Done, func); 
    _sync = sync; 
} 

private void InvokeCompleteEventArgs(T result) 
{ 
    var args = new EventArgs(result, null, AsyncMethod.GetEventByClass, timer.Elapsed); 
    if (OnOperationComplete != null) 
    _sync.Invoke(OnOperationComplete, new object[]{null, args}); 
} 

Control класс реализует ISynchronizeInvoke, так что вы можете передать this указатель из Form или Control, который вызывает помощника и имеет делегата обработчика событий,

+0

'ISynchronizeInvoke' является устаревшим интерфейсом и не должен использоваться. –

+0

@ Stephen - ссылка? – dkackman

3

Я рекомендую использовать Task класс, а не BackgroundWorker, но either would be greatly superior to Control.Invoke or Dispatcher.Invoke.

Пример:

internal class AsyncHelper<T> 
{ 
    private readonly Stopwatch timer = new Stopwatch(); 
    private readonly TaskScheduler ui; 

    // This should be called from a UI thread. 
    internal AsyncHelper() 
    { 
    this.ui = TaskScheduler.FromCurrentSynchronizationContext(); 
    } 

    internal event DownloadCompleteHandler OnOperationComplete; 

    internal Task Start(Func<T> func) 
    { 
    timer.Start(); 
    Task.Factory.StartNew(func).ContinueWith(this.Done, this.ui); 
    } 

    private void Done(Task<T> task) 
    { 
    timer.Stop(); 
    if (task.Exception != null) 
    { 
     // handle error condition 
    } 
    else 
    { 
     InvokeCompleteEventArgs(task.Result); 
    } 
    } 

    private void InvokeCompleteEventArgs(T result) 
    { 
    var args = new EventArgs(result, null, AsyncMethod.GetEventByClass, timer.Elapsed); 
    if (OnOperationComplete != null) OnOperationComplete(null, args); 
    } 

    internal delegate void DownloadCompleteHandler(object sender, EventArgs e); 
} 

Это очень похоже на BackgroundWorker, хотя (кроме того, что вы добавляете таймер). Вы можете рассмотреть возможность использования только BackgroundWorker.

+0

Вы прочитали? Мои «три страницы» включают в себя полное решение с пользовательским интерфейсом, поддержку отмены, правильную сортировку ошибок и т. Д. Ни одно из которых не включает ваше решение. –

+0

Я обновил свой ответ простым решением. –

+0

Мне это нравится, но не знаю, как реализовать, не могли бы вы привести пример использования? – 2010-09-04 17:25:44

Смежные вопросы