Reputation: 1155
I have several threads (doing server work) that need to queue work to be done on the main thread (they need access to an API which can only be called from the main thread).
Using the Task system makes sense to me, as the server threads can produce tasks which use this main-thread-only API, queue them to run on the main thread and await them. The main thread can spin, popping tasks from a ConcurrentQueue and run them as they come in.
Essentially this is a bit of an inversion of the usual pattern, where tasks are run in parallel across a thread pool. Here, the Tasks are created from multiple other threads and queued up to run on the one main thread.
Now for a wrinkle: some of these tasks might spawn sub tasks which themselves need to be run on the main thread. eg:
# On server thread:
Task taskA = new Task(async () => {
Task taskB = new Task(() => Console.WriteLine("some sub work"));
MainThreadWorkQueue.BeginInvoke(taskB);
await taskB;
});
MainThreadWorkQueue.BeginInvoke(taskA);
await taskA;
This might be because one of the top level tasks is using library code which itself needs to run on the main thread.
So I end up with a tree-like structure of Tasks, where root level tasks need to block waiting on leaf tasks to finish, but the root tasks are also creating these leaf tasks dynamically as they execute.
The problem is I'm not sure how to write the main thread code. I was thinking something like:
// Main thread:
while(true) {
Task currentTask;
while (MainThreadWorkQueue.TryDequeue(out currentTask)) {
currentTask.Wait();
}
// Do other work
}
However this will deadlock in the taskA example above, as taskB gets queued but taskA will never finish to allow taskB's work to begin.
Ideally I'd like to do something like:
// Main thread:
while(true) {
Task currentTask;
while (MainThreadWorkQueue.TryDequeue(out currentTask)) {
currentTask.RunUntilFinishedOrBlocked();
if (currentTask.IsBlockedOnOtherTask) {
MainThreadWorkQueue.Enqueue(currentTask)
}
}
// Do other work
}
Is there a way to run a task until it's blocked on another task? Or a different way to approach this problem?
Upvotes: 1
Views: 594
Reputation: 457137
You should never use the Task
constructor. It has literally zero use cases.
In your case, the type you're looking for isn't Task
; it's a delegate:
I'm trying to use Tasks to represent work units
Task
is the incorrect type for this. Sounds more like Func<Task>
.
I'd like to run taskA until it blocks, and then run taskB, then continue running taskA.
In that case, you should use a single-threaded SynchronizationContext
. You can do this either by running these tasks on a UI thread, or by running them within something like AsyncContext
. AsyncContext
has a built-in work queue that it processes until complete.
Upvotes: 2