Task.Factory.ContinueWhenAny在任何任务完成时继续,无异常

时间:2015-06-25 10:41:40

标签: c# async-await task-parallel-library .net-4.5

我的应用程序中有3个任务负责从数据库中获取数据 直到现在我已经一个接一个地执行了所有任务。如果第一次完成并有结果,那么这是我的数据,如果现在我开始第二个任务,我再次检查。

最近,我发现可以启动多项任务的信息,并在其中一项任务完成Task.Factory.ContinueWhenAny后继续。如果我的所有任务都没有抛出任何异常,这样就可以正常工作,但如果任何任务失败,我将无法获得我想要的结果。

例如:

var t1 = Task.Factory.StartNew(() =>
{
    Thread.Sleep(5000);
    return 1;
});

var t2 = Task.Factory.StartNew(() =>
{
    Thread.Sleep(2000);
    throw new Exception("My error");
    return 2;
});

var t3 = Task.Factory.StartNew(() =>
{
    Thread.Sleep(4000);
    return 3;
});

Task.Factory.ContinueWhenAny(new[] {t1, t2,t3}, (t) =>
{
    Console.WriteLine(t.Result);
});

此代码启动3个任务并等待其中一个完成。 因为t2在2秒后抛出异常,所以它是ContinueWhenAny中可用的异常。

从上面的代码我想在t.Result得到3 是否有选项只在任务成功完成后继续?像Task.Factory.ContinueWhenAnyButSkipFailedTasks

这样的东西

编辑1 这是我现在基于@Nitram答案的解决方案:

var t1 = Task.Factory.StartNew(() =>
{
    var rnd = new Random();
    Thread.Sleep(rnd.Next(5,15)*1000);
    throw new Exception("My error");
    return 1;
});

var t2 = Task.Factory.StartNew(() =>
{
    Thread.Sleep(2000);
    throw new Exception("My error");
    return 2;
});

var t3 = Task.Factory.StartNew(() =>
{
    throw new Exception("My error");
    return 3;
});

var tasks = new List<Task<int>> { t1, t2, t3 };

Action<Task<int>> handler = null;

handler = t =>
{
    if (t.IsFaulted)
    {
        tasks.Remove(t);
        if (tasks.Count == 0)
        {
            throw new Exception("No data at all!");
        }
        Task.Factory.ContinueWhenAny(tasks.ToArray(), handler);
    }
    else
    {
        Console.WriteLine(t.Result);
    }
};

Task.Factory.ContinueWhenAny(tasks.ToArray(), handler);

我现在需要的是当所有任务抛出异常时如何抛出异常? 也许这可以改成单一的方法来返回任务 - 比如子任务?

3 个答案:

答案 0 :(得分:3)

ContinueWhenAny函数有一个过载,可以完成您想要的任务。

只需将TaskContinuationOptions设置为OnlyOnRanToCompletion,即可忽略失败的任务。

Task.Factory.ContinueWhenAny(new[] {t1, t2,t3}, (t) =>
{
    Console.WriteLine(t.Result);
}, TaskContinuationOptions.OnlyOnRanToCompletion);

所以我们得出结论,这个答案实际上是错误的。

从列表中删除任务似乎是我能想到的唯一方法。 我试着把它放到一些代码行中。你走了:

var tasks = new List<Task> {t1, t2, t3};

Action<Task> handler = null;
handler = (Task t) =>
{
    if (t.IsFauled) {
        tasks.Remove(t);
        Task.Factory.ContinueWhenAny(tasks.ToArray, handler);
    } else {
        Console.WriteLine(t.Result);
    }
};
Task.Factory.ContinueWhenAny(tasks.ToArray, handler);

我对C#不是很坚定,但我希望它会给你一个想法。基本上发生的是每次处理出现故障的任务时,此任务将从已知任务列表中删除,并且该函数将等待下一个任务。

好的,现在整个.NET 4.5和async - await模式。 await基本上使你能够在等待到延续之后注册所写的内容。

所以这与async - await几乎相同。

var tasks = new List<Task> {t1, t2, t3};
while (tasks.Any()) 
{
    var finishedTask = await Task.WhenAny(tasks);
    if (finishedTask.IsFaulted)
    {
        tasks.Remove(finishedTask);
    }
    else
    {
        var result = await finishedTask;
        Console.WriteLine(result);
        return;
    }
}

唯一的区别是外部函数需要是async函数。这意味着在遇到第一个await时,外部函数将返回保持延续的Task

您可以添加一个阻止的周围函数,直到完成此功能。 async - await模式使您能够编写“看起来”像同步代码的异步非阻塞代码。

另外,我建议您使用Task.Run函数来生成您的任务,而不是TaskFactory。它稍后会解决一些问题。 ; - )

答案 1 :(得分:3)

如果您使用的是.NET 4.5,则可以使用Task.WhenAny轻松实现所需目标:

public async Task<int> GetFirstCompletedTaskAsync()
{
    var tasks = new List<Task> 
    {
        Task.Run(() =>
        {
            Thread.Sleep(5000);
            return 1;
        }),
        Task.Run(() =>
        {
            Thread.Sleep(2000);
            throw new Exception("My error");
        }),
        Task.Run(() =>
        {
            Thread.Sleep(4000);
            return 3;
        }),
    };

    while (tasks.Count > 0)
    {
        var finishedTask = await Task.WhenAny(tasks);
        if (finishedTask.Status == TaskStatus.RanToCompletion)
        {
            return finishedTask
        }

        tasks.Remove(finishedTask);
    }
    throw new WhateverException("No completed tasks");
}

答案 2 :(得分:2)

如果只是这样做(至少它对我有用)怎么办:

        bool taskFinishedFlag = false;

        Task t1 = Task.Factory.StartNew(() => { Thread.Sleep(4000); return 1; });

        Task t2 = Task.Factory.StartNew(() => { Thread.Sleep(2000); 
                                                throw new Exception("");return 2; });

        Task t3 = Task.Factory.StartNew(() => { Thread.Sleep(4000); return 3; });

        Task<int>[] Tasks = new[] { t1, t2, t3 };

        for (int i = 0; i < Tasks.Length; i++)
        {
            Tasks[i].ContinueWith((t) =>
                {
                    if (taskFinishedFlag) return;
                    taskFinishedFlag = true;
                    Console.WriteLine(t.Result);
                }, TaskContinuationOptions.NotOnFaulted);
        }