What is a correct way to queue complex tasks in wp8?
The tasks consists of the following:
- Showing a
ProgressIndicator
through updating a model variable
- Fetching or storing data to a wcf service (
UploadStringAsync
)
- Updating potentially data bound model with the result from
UploadStringCompleted
.
- Hiding the
ProgressIndicator
through updating a model variable
Currently I've been working with a class owning a queue of command objects, running a single thread that is started when an item is added if it's not already running.
I have however problems with waiting for tasks or subtasks where the code stops running.
Previously I've used async await, but a few levels down the behaviour was becoming more and more unpredictable.
What I want is the main thread being able to create and queue command objects.
The command objects should run one at a time, not starting a new one until the previous one is completely finished.
The command objects should be able to use the dispatcher to access the main thread if neccesary.
If you use async
/await
, there's no need for another thread (since you have no CPU-bound processing).
In your case, it sounds like you just need a queue of asynchronous delegates. The natural type of an asynchronous delegate is Func<Task>
(without a return value) or Func<Task<T>>
(with a return value). This little tip is unfortunately not well-known at this point.
So, declare a queue of asynchronous delegates:
private readonly Queue<Func<Task>> queue = new Queue<Func<Task>>();
Then you can have a single "top-level" task that just (asynchronously) processes the queue:
private Task queueProcessor;
The queueProcessor
can be null
whenever there's no more items. Whenever it's not null
, it'll represent this method:
private async Task ProcessQueue()
{
try
{
while (queue.Count != 0)
{
Func<Task> command = queue.Dequeue();
try
{
await command();
}
catch (Exception ex)
{
// Exceptions from your queued tasks will end up here.
throw;
}
}
}
finally
{
queueProcessor = null;
}
}
Your Enqueue
method would then look like this:
private void Enqueue(Func<Task> command)
{
queue.Enqueue(command);
if (queueProcessor == null)
queueProcessor = ProcessQueue();
}
Right now, I have the exception handling set up like this: any queued command that throws an exception will cause the queue processor to stop processing (with the same exception). This may not be the best behavior for your application.
You can use it like this (with either a lambda or an actual method, of course):
Enqueue(async () =>
{
ShowProgressIndicator = true;
ModelData = await myProxy.DownloadStringTaskAsync();
ShowProgressIndicator = false;
});
Note the use of DownloadStringTaskAsync
. If you write TAP wrappers for your EAP members, your async
code will be more "natural-looking" (i.e., simpler).
This is sufficiently complex that I'd recommend putting it into a separate class, but you'd want to decide how to handle (and surface) errors first.