问题
I am trying to chain Task<T>
objects in C# as done in JavaScript and without blocking the UI thread.
I see there is a similar question here, but it uses the non-generic Task
object as a return type of the process functions. I try to do the same with Task<T>
.
I also see that here is a closer question to my needs, but the accepted answer seems to use .Result
twice, which I guess will block the UI thread. Also, note that I chain tasks dynamically, so I can't follow some easy workarounds. And also, the Then
implementation given here seems synchronous too (I am not sure if simply changing the TaskContinuationOptions
on this old sample code will do what I want).
Here is what I have right now, but I can't even make it compile without blocking the thread:
// Initial dummy task.
private Task<bool> taskChain = Task.Factory.StartNew<bool>(() => true);
// Chain dynamically on button click.
private async void DoSth_Click(object sender, RoutedEventArgs e)
{
var data = ....;
System.Threading.Tasks.Task<bool> del = async (t) => { return await ConvertAsync(data); };
taskChain = taskChain.ContinueWith<bool>(() => del);
var res = await taskChain;
}
I have tried various different approaches, but I don't see how I can turn Task<T>
to Func<Task<T>, T>
that ContinueWith<bool>()
seems to require (at least without doing some nasty UI thread blocking operation).
I would expect this to be easy, but I don't quite see the solution here... Isn't there a good and easy way to do this?
(Note: I guess I should probably call Unwrap()
after the ContinueWith()
but this seems like a detail at this point...)
回答1:
UnWrap
is your friend here. It'll allow you to have a continuation method that resolves to a Task
, and then get a Task
that represents that task before the continuation has even fired.
Also note that FromResult
should be used to create an already completed task.
private Task<bool> taskChain = Task.FromResult(true);
private async void DoSth_Click(object sender, RoutedEventArgs e)
{
var data = CreateData();
taskChain = taskChain.ContinueWith(t => ConvertAsync(data))
.Unwrap();
var res = await taskChain;
}
Note that I'd advise against doing this in-line in a click handler. Create a class that is able to queue tasks, and then use that. Of course, such a queue class is just following this same pattern:
public class TaskQueue
{
private Task previous = Task.FromResult(false);
private object key = new object();
public Task<T> Enqueue<T>(Func<Task<T>> taskGenerator)
{
lock (key)
{
var next = previous.ContinueWith(t => taskGenerator()).Unwrap();
previous = next;
return next;
}
}
public Task Enqueue(Func<Task> taskGenerator)
{
lock (key)
{
var next = previous.ContinueWith(t => taskGenerator()).Unwrap();
previous = next;
return next;
}
}
}
This would allow you to write:
private TaskQueue taskQueue = new TaskQueue();
private async void DoSth_Click(object sender, RoutedEventArgs e)
{
var data = CreateData();
var res = await TaskQueue.Enqueue(ConvertAsync(data));
}
Now your mechanism of queuing tasks is separated from the business logic of what this click handler needs to do.
回答2:
The easiest way to "chain" is to just await
:
// Initial dummy task.
private Task taskChain = Task.FromResult(true);
// Chain dynamically on button click.
private async void DoSth_Click(object sender, RoutedEventArgs e)
{
var data = ....;
taskChain = ChainedConvertAsync(taskChain, data);
var res = await taskChain;
...
}
private async Task<Result> ChainedConvertAsync(Task precedent, Data data)
{
await precedent;
return await ConvertAsync(data);
}
On a side note, avoid StartNew
and ContinueWith
; they are dangerous APIs due to their default schedulers.
来源:https://stackoverflow.com/questions/36021645/chaining-taskt-dynamically-and-without-blocking-the-thread