c# windows-phone windows-phone-7.1 async-await

c# - Ejecutar el método "async" en un hilo de fondo



windows-phone windows-phone-7.1 (2)

Estoy intentando ejecutar un método "asíncrono" desde un método ordinario:

public string Prop { get { return _prop; } set { _prop = value; RaisePropertyChanged(); } } private async Task<string> GetSomething() { return await new Task<string>( () => { Thread.Sleep(2000); return "hello world"; }); } public void Activate() { GetSomething.ContinueWith(task => Prop = task.Result).Start(); // ^ exception here }

La excepción lanzada es:

Inicio no puede ser llamado en una tarea de continuación.

¿Qué significa eso, de todos modos? ¿Cómo puedo simplemente ejecutar mi método asíncrono en un subproceso en segundo plano, enviar el resultado de nuevo al subproceso de la interfaz de usuario?

Editar

También probé Task.Wait , pero la espera nunca termina:

public void Activate() { Task.Factory.StartNew<string>( () => { var task = GetSomething(); task.Wait(); // ^ stuck here return task.Result; }).ContinueWith(task => { Prop = task.Result; }, TaskScheduler.FromCurrentSynchronizationContext()); GetSomething.ContinueWith(task => Prop = task.Result).Start(); }


ADVERTENCIA sobre el uso de FromCurrentSynchronizationContext:

Ok, Cory sabe cómo hacerme reescribir la respuesta :).

¡Así que el principal culpable es en realidad el FromCurrentSynchronizationContext! Cada vez que StartNew o ContinueWith se ejecuta en este tipo de planificador, se ejecuta en el subproceso de la interfaz de usuario. Uno puede pensar:

Bien, comencemos las operaciones subsiguientes en la interfaz de usuario, cambiemos algunos controles, generemos algunas operaciones. Pero a partir de ahora TaskScheduler.Current no es nulo y si algún control tiene algunos eventos, eso genera algo de StartNew que se espera que se ejecute en ThreadPool, luego desde ahí va mal. Los aps de UI suelen ser complejos, incómodos por mantener la certeza, de que nada llamará a otra operación StartNew, por ejemplo, aquí:

public partial class Form1 : Form { public static int Counter; public static int Cnt => Interlocked.Increment(ref Counter); private readonly TextBox _txt = new TextBox(); public static void WriteTrace(string from) => Trace.WriteLine($"{Cnt}:{from}:{Thread.CurrentThread.Name ?? "ThreadPool"}"); public Form1() { InitializeComponent(); Thread.CurrentThread.Name = "ThreadUI!"; //this seems to be so nice :) _txt.TextChanged += (sender, args) => { TestB(); }; WriteTrace("Form1"); TestA(); WriteTrace("Form1"); } private void TestA() { WriteTrace("TestA.Begin"); Task.Factory.StartNew(() => WriteTrace("TestA.StartNew")) .ContinueWith(t => { WriteTrace("TestA.ContinuWith"); _txt.Text = @"TestA has completed!"; }, TaskScheduler.FromCurrentSynchronizationContext()); WriteTrace("TestA.End"); } private void TestB() { WriteTrace("TestB.Begin"); Task.Factory.StartNew(() => WriteTrace("TestB.StartNew - expected ThreadPool")) .ContinueWith(t => WriteTrace("TestB.ContinueWith1 should be ThreadPool")) .ContinueWith(t => WriteTrace("TestB.ContinueWith2")); WriteTrace("TestB.End"); } }

  1. Form1: ThreadUI! - OKAY
  2. TestA.Begin: ThreadUI! - OKAY
  3. TestA.End: ThreadUI! - OKAY
  4. Form1: ThreadUI! - OKAY
  5. TestA.StartNew: ThreadPool - OK
  6. TestA.ContinuWith: ThreadUI! - OKAY
  7. TestB.Begin: ThreadUI! - OKAY
  8. TestB.End: ThreadUI! - OKAY
  9. TestB.StartNew - ThreadPool esperado: ThreadUI! - ¡PODRÍA SER INESPERADO!
  10. TestB.ContinueWith1 debe ser ThreadPool: ThreadUI! - ¡PODRÍA SER INESPERADO!
  11. TestB.ContinueWith2: ThreadUI! - OKAY

Tenga en cuenta que las tareas devueltas por:

  1. método asíncrono,
  2. Task.Fatory.StartNew,
  3. Tarea.

no se puede iniciar! Ya son tareas calientes ...


Para arreglar su ejemplo específicamente:

public void Activate() { Task.Factory.StartNew(() => { //executes in thread pool. return GetSomething(); // returns a Task. }) // returns a Task<Task>. .Unwrap() // "unwraps" the outer task, returning a proxy // for the inner one returned by GetSomething(). .ContinueWith(task => { // executes in UI thread. Prop = task.Result; }, TaskScheduler.FromCurrentSynchronizationContext()); }

Esto funcionará, pero es de la vieja escuela.

La forma moderna de ejecutar algo en un subproceso en segundo plano y enviarlo nuevamente al subproceso de la interfaz de usuario es usar Task.Run() , async y await :

async void Activate() { Prop = await Task.Run(() => GetSomething()); }

Task.Run iniciará algo en un subproceso de grupo de subprocesos. Cuando await algo, vuelve automáticamente al contexto de ejecución que lo inició. En este caso, su hilo de interfaz de usuario.

En general, nunca debería necesitar llamar a Start() . Prefiere los métodos async , Task.Run y Task.Factory.StartNew , todos los cuales inician las tareas automáticamente. Las continuaciones creadas con await o ContinueWith también se inician automáticamente cuando sus padres completan.