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

Mis*_*siu 7 c# task-parallel-library async-await .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);
});
Run Code Online (Sandbox Code Playgroud)

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

从上面的代码我想得到3 t.Result.
是否有选项只在任务成功完成后继续?就像是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);
Run Code Online (Sandbox Code Playgroud)

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

Nit*_*ram 3

该函数有一个重载来ContinueWhenAny执行您想要的操作。

只需设置TaskContinuationOptionsOnlyOnRanToCompletion,失败的任务将被忽略。

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

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

从列表中删除任务似乎是我能想到的唯一方法。我尝试将其放入某些代码行中。干得好:

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);
Run Code Online (Sandbox Code Playgroud)

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

async好的,现在介绍一下 .NET 4.5 和-模式的全部内容awaitawait基本上使您能够将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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

另外,我建议您使用该Task.Run函数来生成任务,而不是使用TaskFactory. 以后会避免一些问题。;-)