UnobservedTaskException
will be called for all unobserved Task
exceptions, so it's a good place for logging like this. However, it's not great because, depending on your program logic, you may see spurious messages; e.g., if you Task.WhenAny
and then ignore the slower task, then a any exceptions from that slower task should be ignored but they do get sent to UnobservedTaskException
. As an alternative, consider placing a ContinueWith
on your top-level task (the one returned from StartLoopAsync
).
Your call to StartLoopAsync
looks fine to me, assuming it's properly asynchronous. You could use TaskRun
(e.g., Task.Run(() => _scheduler.StartLoopAsync())
- no Wait
is necessary), but the only benefit would be if StartLoopAsync
itself could raise an exception (as opposed to faulting its returned task) or if it took too long before the first await
.
ConfigureAwait(false)
is only useful when doing an await
, as you surmised.
My AsyncContextThread
is designed for this kind of situation, but it was also designed to be very simple. :) AsyncContextThread
provides an independent thread with a main loop similar to your scheduler, complete with a TaskScheduler
, TaskFactory
, and SynchronizationContext
. However, it is simple: it only uses a single thread, and all of the scheduling/context points back to that same thread. I like that because it greatly simplifies thread safety concerns while also allowing concurrent asynchronous operations - but it is not making full use of the thread pool so, e.g., CPU-bound work would block the main loop (similar to a UI thread scenario).
In your situation, it sounds like AsyncContextThread
may let you remove/simplify some of the code you've already written. But on the other hand, it is not multithreaded like your solution is.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…