Chaining `Task<T>` dynamically and without blocking the thread

跟風遠走 提交于 2021-02-08 06:54:17

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!