TaskCanceledException using Task<string>
本文关键字:string gt using lt TaskCanceledException Task | 更新日期: 2023-09-27 17:56:08
我正在做的事情的概述:在一个循环中,我正在启动一个新的Task<string>
并将其添加到List<Task<string>>
中。 问题是,返回字符串后,任务抛出System.Threading.Tasks.TaskCanceledException
,我不知道为什么。 以下是我正在做的事情的修剪版本
public async Task<string> GenerateXml(object item)
{
using (var dbContext = new DatabaseContext())
{
//...do some EF dbContext async calls here
//...generate the xml string and return it
return "my xml data";
}
}
var tasks = new List<Task<string>>();
我的循环如下所示:
foreach (var item in items)
{
tasks.Add(Task.Run(() => GenerateXml(item).ContinueWith((t) => { return ""; }, TaskContinuationOptions.OnlyOnFaulted)));
//also tried:
tasks.Add(Task.Run(async () => await GenerateXml(item).ContinueWith((t) => { return ""; }, TaskContinuationOptions.OnlyOnFaulted)));
//both generate the same exception
//after looking at my code, I was using the ContinueWith on the GenerateXml method call, which should still work, right?
//I moved the continue with to the `Task.Run` and still get the exception.
}
Task.WaitAll(tasks.ToArray()); //this throws the AggregateException which contains the TaskCanceledException
当我单步执行代码时,它命中return "my xml data";
但抛出异常。
我试图避免ContinueWith
是当我循环每个任务并获得结果时,它不会抛出与WaitAll
抛出的相同AggregateException
。
这是一个工作控制台应用程序,它抛出... 我知道问题出在ContinueWith
,但为什么呢?
class Program
{
static void Main(string[] args)
{
var program = new Program();
var tasks = new List<Task<string>>();
tasks.Add(Task.Run(() => program.GenerateXml().ContinueWith((t) => { return ""; }, TaskContinuationOptions.OnlyOnFaulted)));
Task.WaitAll(tasks.ToArray()); //this throws the AggregateException
foreach (var task in tasks)
{
Console.WriteLine(task.Result);
}
Console.WriteLine("finished");
Console.ReadKey();
}
public async Task<string> GenerateXml()
{
System.Threading.Thread.Sleep(3000);
return "my xml data";
}
}
正如回答者 Avram 所暗示的那样,您会收到异常,因为您的列表包含的不是运行 GenerateXml()
方法的任务,而是那些运行该方法的任务的延续。
由于这些任务仅在GenerateXml()
引发异常时运行,因此如果对GenerateXml()
的任何调用成功,则至少一个继续任务将不会运行。相反,它通过被取消(即当其先前任务成功完成时)来完成,因此对 WaitAll()
的调用会看到该取消并引发聚合异常。
恕我直言,解决这个问题的最佳方法是坚持使用 async
/await
模式。 即,与其直接使用ContinueWith()
,不如编写代码,使其具有可读性和表达性。在这种情况下,我将编写一个包装器async
方法来调用 GenerateXml()
方法,捕获发生的任何异常,并在该情况下返回 ""
值。
这是您的MCVE的修改版本,显示了我的意思:
class Program
{
static void Main(string[] args)
{
var tasks = new List<Task<string>>();
tasks.Add(SafeGenerateXml());
Task.WaitAll(tasks.ToArray());
foreach (var task in tasks)
{
Console.WriteLine(task.Result);
}
Console.WriteLine("finished");
Console.ReadKey();
}
static async Task<string> SafeGenerateXml()
{
try
{
return await GenerateXml();
}
catch (Exception)
{
return "";
}
}
static async Task<string> GenerateXml()
{
await Task.Delay(3000);
return "my xml data";
}
}
恕我直言,这更符合 C# 中的新async
习语,更不容易失败,并且更容易理解到底发生了什么(即通过完全避免ContinueWith()
,你甚至没有机会混淆哪些任务正在等待,就像你在原始代码中显然所做的那样)。
您正在运行第二个任务。
.继续((t) 任务。
要运行正确的代码,您需要重构代码。
像这样拆分行:
Task<string> t1 = Task.Run(() => program.GenerateXml());
t1.ContinueWith((t) => { return ""; }, TaskContinuationOptions.OnlyOnFaulted);
tasks.Add(t1);
您可以像这样重构任务:(用于错误处理)
tasks.Add(program.GenerateXml().ContinueWith(t => {return t.IsFaulted? "": t.Result; }));
由于您使用.ContinueWith
作为从异常中恢复的一种方式,因此只需添加 try {} catch
语句即可。
var tasks = new List<Task<string>>();
foreach (var item in items)
{
var closure = item;
var task =
Task.Factory.StartNew(
async () =>
{
try
{
return await GenerateXml(closure);
}
catch (Exception exception)
{
//log
return "";
}
}).Unwrap();
tasks.Add(task);
}
Task.WaitAll(tasks.ToArray());
但是,如果我是你,我会将此逻辑隐藏在GenerateXml
方法中。只要您将默认值(此处为空字符串)视为有效值,就应该没问题。
var tasks = items.Select(item => Task.Run(() => GenerateXml(item))).ToList();