c# - understanding - Llamar a métodos asíncronos desde un servicio de Windows
task.run async c# (2)
Tengo un servicio de Windows escrito en C # que dispara periódicamente trabajos en segundo plano. Por lo general, en un momento dado, varias docenas de Tareas fuertemente enlazadas de E / S (descarga de archivos grandes, etc.) se ejecutan en paralelo. El servicio se ejecuta en un servidor web relativamente ocupado (necesario por ahora), y creo que podría beneficiarse enormemente en términos de conservación de subprocesos el uso de API asíncronas tanto como sea posible.
La mayor parte de este trabajo está hecho. Todos los trabajos ahora son totalmente asíncronos (aprovechando HttpClient, etc.), al igual que el bucle principal de trabajo (con dosis elevadas de Task.Delay). Todo lo que queda es descubrir cómo iniciar de forma correcta y segura el bucle principal desde el inicio del servicio. Esencialmente, es el dilema de las llamadas asincrónicas desde la sincronización. A continuación se muestra lo que tengo hasta ahora (muy simplificado).
en Program.cs:
static void Main(string[] args) {
TaskScheduler.UnobservedTaskException += (sender, e) => {
// log & alert!
e.SetObserved();
};
ServiceBase.Run(new MyService());
}
en MyService.cs:
protected override void OnStart(string[] args) {
_scheduler.StartLoopAsync(); // fire and forget! will this get me into trouble?
}
Es ese llamado a StartLoopAsync
que me preocupa. No puedo simplemente Wait()
en la tarea devuelta porque OnStart necesita regresar con relativa rapidez. (Los bucles de trabajo deben ejecutarse en un hilo separado.) Un par de pensamientos vienen a la mente:
- ¿Estoy bien cubierto en cuanto a excepciones no observadas al colocar ese controlador en Main?
- ¿Habría algún beneficio al utilizar Task.Run, algo como
Task.Run(() => _scheduler.StartLoopAsync().Wait());
? - ¿Habría algún beneficio al llamar a
_scheduler.StartLoopAsync().ConfigureAwait(false)
aquí? (Lo estoy dudando ya que no hayawait
aquí). - ¿Habría algún beneficio al usar AsyncContextThread de Stephen Cleary en esta situación? No he visto ningún ejemplo de cómo usar esto, y como estoy iniciando un bucle infinito, no sé si la sincronización de la copia de seguridad en algún contexto es incluso relevante aquí.
No es una respuesta en sí misma, pero un año después de publicar esta pregunta, estamos transfiriendo este servicio a un Servicio de Azure Cloud. He encontrado que la plantilla Worker Role de Azure SDK es un muy buen ejemplo de cómo llamar correctamente a async desde la sincronización, proporcionar soporte de cancelación, lidiar con excepciones, etc. No es del todo exagerado con los servicios de Windows. no proporciona un equivalente al método Run
(debe comenzar su trabajo en OnStart
y regresar inmediatamente), pero para lo que vale, aquí está:
public class WorkerRole : RoleEntryPoint
{
private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
private readonly ManualResetEvent runCompleteEvent = new ManualResetEvent(false);
public override void Run() {
Trace.TraceInformation("WorkerRole1 is running");
try {
this.RunAsync(this.cancellationTokenSource.Token).Wait();
}
finally {
this.runCompleteEvent.Set();
}
}
public override bool OnStart() {
// Set the maximum number of concurrent connections
ServicePointManager.DefaultConnectionLimit = 12;
// For information on handling configuration changes
// see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357.
bool result = base.OnStart();
Trace.TraceInformation("WorkerRole1 has been started");
return result;
}
public override void OnStop() {
Trace.TraceInformation("WorkerRole1 is stopping");
this.cancellationTokenSource.Cancel();
this.runCompleteEvent.WaitOne();
base.OnStop();
Trace.TraceInformation("WorkerRole1 has stopped");
}
private async Task RunAsync(CancellationToken cancellationToken) {
// TODO: Replace the following with your own logic.
while (!cancellationToken.IsCancellationRequested) {
Trace.TraceInformation("Working");
await Task.Delay(1000);
}
}
}
UnobservedTaskException
llamará a Task
excepción UnobservedTaskException
para todas las excepciones de Task
no observadas, por lo que es un buen lugar para el registro de este tipo. Sin embargo, no es bueno porque, dependiendo de la lógica de su programa, puede ver mensajes espurios; por ejemplo, si realiza Task.WhenAny
y luego ignora la tarea más lenta, entonces cualquier excepción de esa tarea más lenta debe ignorarse, pero se envían a la UnobservedTaskException
. Como alternativa, considere colocar un ContinueWith
en su tarea de nivel superior (la que se devolvió desde StartLoopAsync
).
Su llamada a StartLoopAsync
parece bien, asumiendo que es correctamente asíncrono. Puede usar TaskRun
(por ejemplo, Task.Run(() => _scheduler.StartLoopAsync())
- no es necesario Wait
)), pero el único beneficio sería si el propio StartLoopAsync
pudiera generar una excepción (en lugar de fallar en su tarea devuelta) O si pasaba demasiado tiempo antes de la primera await
.
ConfigureAwait(false)
await
ConfigureAwait(false)
solo es útil cuando se hace una await
, como supuso.
My AsyncContextThread
está diseñado para este tipo de situación, pero también fue diseñado para ser muy simple. :) AsyncContextThread
proporciona un subproceso independiente con un bucle principal similar a su programador, completo con un TaskScheduler
, TaskFactory
y SynchronizationContext
. Sin embargo, es simple: solo usa un solo hilo, y toda la programación / contexto apunta al mismo hilo. Me gusta eso porque simplifica enormemente las preocupaciones sobre la seguridad de los subprocesos y también permite operaciones asíncronas simultáneas, pero no está haciendo un uso completo del conjunto de subprocesos, por lo que, por ejemplo, el trabajo vinculado a la CPU bloquearía el bucle principal (similar a un escenario de subprocesos de la interfaz de usuario).
En su situación, parece que AsyncContextThread
puede permitirle eliminar / simplificar parte del código que ya ha escrito. Pero, por otro lado, no es multiproceso como lo es su solución.