Task.Factory.Continute当任何任务无一例外地完成时,都可以继续

本文关键字:完成时 都可以 继续 无一例外 任务 Factory Continute 任何 Task | 更新日期: 2023-09-27 17:59:32

我的应用程序中有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);

我现在需要的是,当所有任务都抛出异常时,如何抛出异常
也许这可以变成一个返回任务的方法——比如子任务?

Task.Factory.Continute当任何任务无一例外地完成时,都可以继续

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#不是很坚定,但我希望它能给你一个想法。基本上发生的情况是,每次处理出现故障的任务时,该任务都会从已知任务列表中删除,函数会等待下一个任务。

好了,现在是.NET4.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。稍后它将避免出现一些问题。;-)

如果您使用的是.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");
}

如果只是简单地这样做(至少对我有效):

        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);
        }      
相关文章: