2010-09-04 11 views
7

Estoy escribiendo una clase de ayuda asincrónica muy simple para acompañar mi proyecto. El propósito de la clase es que permite que un método se ejecute en un hilo de fondo. Aquí está el código;C# devolución de llamada asíncrona todavía en el hilo de fondo ... ayuda! (preferiblemente sin 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 
    } 

El resultado de la tarea se devuelve a través del evento OnOperationComplete. El problema es que cuando se levanta el evento, todavía está en el hilo de fondo. Es decir. si intento ejecutar este código (a continuación) aparece un error de cruce de hilos;

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

Por favor, comunique sus ideas.

+0

¿Estás en .NET 2.0, 3.5, 4.0? ¿Y está creando una aplicación WinForms, WPF, Silverlight? –

+0

.NET 4.0, biblioteca de clases –

Respuesta

5

Utilice la clase BackgroundWorker. En esencia, hace lo mismo que quieres.

 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

También puede informar la configuración del progreso: _worker.WorkerReportsProgress = true; // AND _worker.ProgressChanged + = Progress_Changed; –

+0

Me olvidé un poco ... para ejecutar realmente el trabajador tienes que llamar al método _worker.RunWorkerAsync(). –

+0

Hay un pequeño enlace de "editar" debajo de su respuesta. :) – Timwi

0

Es necesario invocar su evento en el subproceso de interfaz de usuario,

WinForms

Form1.BeginInvoke(...);

WPF

Dispatcher.BeginInvoke(...);

+0

¿Lo estás sugiriendo ?; OnOperationComplete.BeginInvoke

+0

'ISynchronizeInvoke' es una interfaz desactualizada y no debe utilizarse. –

+1

@Jon, no, él sugiere que llame al método BeginInvoke o Invoke que se encuentra en un control visual. De esta forma, coordinará la llamada al hilo de la GUI y eso le permitirá manipular el estado de la GUI. 'Delegate.BeginInvoke' y' Control.BeginInvoke' no es lo mismo. –

0

Utilice la clase BackgroundWorker, que está básicamente reimplementar se aquí.

0

A menos que usted construye su clase de ayuda para hacer el cambio de contexto interno que siempre tendrá que invocar en el controlador de eventos porque en el código anterior que la recaudación del evento en la rosca no ui.

Para hacer eso, su ayudante necesita saber cómo volver al hilo de la interfaz de usuario. Puede pasar un ISynchronizeInvoke al ayudante y luego usarlo cuando haya terminado. Somwthing como:

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

La clase Control implementa ISynchronizeInvoke para que pueda pasar el this puntero desde el Form o Control que llama al ayudante y tiene el delegado controlador de eventos,

+0

'ISynchronizeInvoke' es una interfaz desactualizada y no debe utilizarse. –

+0

@Stephen - referencia? – dkackman

3

recomiendo el uso de la clase Task en lugar de BackgroundWorker, pero either would be greatly superior to Control.Invoke or Dispatcher.Invoke.

Ejemplo:

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

Esto es muy similar a BackgroundWorker, aunque (excepto que está agregando un temporizador). Es posible que desee considerar el uso de BackgroundWorker.

+0

¿Lo leíste? Mis "tres páginas" incluyen una solución completa con interfaz de usuario, soporte para cancelación, clasificación correcta de errores, etc. Ninguna de las cuales incluye su solución. –

+0

He actualizado mi respuesta con una solución simple. –

+0

Me gusta esto pero no estoy seguro de cómo implementarlo, ¿podría dar ejemplo de uso? –

Cuestiones relacionadas