动态链接`Task <t>`而不阻塞线程

时间:2016-03-15 20:39:28

标签: c# .net asynchronous task

我正在尝试在C#中链接Task<T>对象,就像在JavaScript中完成一样,而不会阻止UI线程。

我看到有类似问题here,但它使用非通用Task对象作为进程函数的返回类型。我尝试对Task<T>执行相同操作。

我也看到here对我的需求是一个更接近的问题,但是接受的答案似乎使用了.Result两次,我想这将阻止UI线程。另外,请注意我动态链接任务,因此我无法遵循一些简单的解决方法。此外,给定hereThen实现似乎也是同步的(我不确定是否只是更改此旧示例代码上的TaskContinuationOptions将执行我想要的操作。)

这是我现在所拥有的,但我甚至无法在不阻塞线程的情况下进行编译:

    // 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;
    }

我尝试了各种不同的方法,但我看不出如何将Task<T>变为Func<Task<T>, T> ContinueWith<bool>()似乎需要(至少没有做一些令人讨厌的UI线程阻塞)操作)。

我希望这很简单,但我在这里看不到解决方案......难道不是有一个好的,简单的方法吗?

(注意:我想我应该在Unwrap()之后拨打ContinueWith(),但这似乎是一个细节......)

2 个答案:

答案 0 :(得分:4)

UnWrap是你的朋友。它允许你有一个解析为Task的延续方法,然后在延续甚至被触发之前得到代表该任务的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;
}

请注意,我建议不要在点击处理程序中在线执行此操作。创建一个能够排队任务的类,然后使用那个。当然,这样的队列类只是遵循相同的模式:

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;
        }
    }
}

这将允许你写:

private TaskQueue taskQueue = new TaskQueue();
private async void DoSth_Click(object sender, RoutedEventArgs e)
{
    var data = CreateData();
    var res = await TaskQueue.Enqueue(ConvertAsync(data));
}

现在,您的排队任务机制与此点击处理程序需要执行的业务逻辑分离。

答案 1 :(得分:3)

最简单的方式来链接&#34;只是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);
}

在旁注中,请避免StartNewContinueWith;由于默认的调度程序,它们是危险的API。