简体   繁体   English

链接`任务 <T> 动态地并且不阻塞线程

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

I am trying to chain Task<T> objects in C# as done in JavaScript and without blocking the UI thread. 我正在尝试像在JavaScript中那样在C#中链接Task<T>对象,并且不阻塞UI线程。

I see there is a similar question here , but it uses the non-generic Task object as a return type of the process functions. 我看到有一个类似的问题在这里 ,但它采用了非通用Task对象的过程函数的返回类型。 I try to do the same with Task<T> . 我尝试对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. 我也看到, 这里是我的需要仔细一问,但接受的答案似乎使用.Result两次,我猜会阻塞UI线程。 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). 而且, 这里给出的Then实现似乎也是同步的(我不确定是否仅更改此旧示例代码上的TaskContinuationOptions就可以实现我想要的功能)。

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). 我尝试了各种不同的方法,但是我看不到如何将Task<T>变成Func<Task<T>, T>ContinueWith<bool>()似乎需要(至少没有做一些讨厌的UI线程)阻止操作)。

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...) (注意:我想我应该在ContinueWith()之后调用Unwrap() ,但是这似乎是一个细节……)

UnWrap is your friend here. UnWrap是您的朋友在这里。 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. 它可以让您拥有一个可以解析为Task的延续方法,然后在延续被触发之前获得一个代表该任务的Task

Also note that FromResult should be used to create an already completed task. 还要注意, FromResult应该用于创建已经完成的任务。

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. 现在,您的排队任务机制与该单击处理程序需要执行的业务逻辑分离。

The easiest way to "chain" is to just await : 最简单的“连锁”方式是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 ; 另外,请避免StartNewContinueWith they are dangerous APIs due to their default schedulers. 由于它们的默认调度程序,它们是危险的API。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM