我的应用程序中有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);
我现在需要的是当所有任务抛出异常时如何抛出异常? 也许这可以改成单一的方法来返回任务 - 比如子任务?
答案 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);
}