C # асинхронный обратный вызов все еще в фоновом потоке ... помогите!(желательно без InvokeRequired) - PullRequest
7 голосов
/ 04 сентября 2010

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


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

Пожалуйста, посоветуйте любые мысли.

Ответы [ 5 ]

5 голосов
/ 04 сентября 2010

Использовать класс 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";
    }
3 голосов
/ 04 сентября 2010

Я рекомендую использовать класс Task вместо BackgroundWorker, но будет либо значительно превосходить Control.Invoke, либо 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 голосов
/ 04 сентября 2010

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

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 голосов
/ 04 сентября 2010

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

0 голосов
/ 04 сентября 2010

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

WinForms

Form1.BeginInvoke(...);

WPF

Dispatcher.BeginInvoke(...);

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...