Task.WhenAll 未完成
Task.WhenAll not completing
我是 C# 中的任务的新手,我遇到了一个我不明白的问题。在下面的代码中,有人可以解释为什么 Unsubscribe 方法末尾的 WriteLine 永远不会被调用。它似乎与前面的 foreach 循环中的 ContinueWith 有关,就好像我评论说它工作正常一样。
谢谢
using System;
using System.Threading;
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Generic;
public class Example
{
public static void Main()
{
Task.Run(() => Unsubscribe());
Console.WriteLine("Method: Main. End");
Console.ReadKey();
}
private static void Unsubscribe()
{
Dictionary<int, string> dicPairsBySubID = new Dictionary<int, string>();
dicPairsBySubID.Add(1, "A");
dicPairsBySubID.Add(2, "B");
dicPairsBySubID.Add(3, "C");
dicPairsBySubID.Add(4, "D");
dicPairsBySubID.Add(5, "E");
List<Task> taskList = new List<Task>();
foreach (KeyValuePair<int, string> sub in dicPairsBySubID)
{
int chanID = sub.Key;
Task task = Task.Run(() => SendUnsubscribe(chanID))
.ContinueWith(tsk =>
{
var flattened = tsk.Exception.Flatten();
flattened.Handle(ex =>
{
Console.WriteLine(string.Format("Error unsubscribing pair {0} (chanID = {2})", sub.Value, chanID), ex);
return true;
});
}, TaskContinuationOptions.OnlyOnFaulted);
taskList.Add(task);
}
Task.WhenAll(taskList).Wait();
// WHY DOES THIS NEVER GET RUN?
Console.WriteLine("Method: Unsubscribe. End");
}
private static async Task SendUnsubscribe(int chanID)
{
await SendAsync(chanID);
Console.WriteLine("Method: SendUnsubscribe. Chan ID: " + chanID);
}
private static async Task SendAsync(int chanID)
{
await Task.Run(() => Thread.Sleep(1000));
Console.WriteLine("Method: SendAsync. Chan ID: " + chanID);
}
}
我对此进行了测试。我在 WhenAll
处遇到异常,说任务已取消。这使得所有这些都有意义:
- 正如 Default 的回答所指出的,您正在将
ContinueWith
任务添加到 taskList
。
ContinueWith
任务被取消,因为它应该 运行 OnlyOnFaulted
,并且没有发生异常。
- 由于抛出异常(未处理),您的
Unsubscribe
的其余部分不是 运行。
您可以采取以下措施来改善这一点:
不要仅对 运行 异步方法使用 Task.Run。您可以这样做:
taskList.Add(SendUnsubscribe(chanID));
在您的 SendUnsubscribe
方法中使用 try
/catch
,而不是在任务中使用 ContinueWith
。
等待 WhenAll
而不是使用 Wait()
:
await Task.WhenAll(taskList);
如果您认为 SendUnsubscribe
无法处理任何可能发生的异常,您总是可以将 await Task.WhenAll(taskList);
包装在 try
/catch
块中。
你的任务抛出一个你没有捕捉到的异常,因为:
ContinueWith
也是 return 一个任务(然后放在 taskList
中)
- 您已声明当前一个任务出现故障时(由于
TaskContinuationOptions.OnlyOnFaulted
),该任务仅应 运行
- 你给出的例子没有进入故障状态
- 当 ContinueWith 任务由于这些选项而不是 运行 时 enters the state
Cancelled
- 等待取消的任务抛出异常
- 在您的 Main 方法中,
Task.Run(() => Unsubscribe())
调用的 return 值未被 await
编辑,因此从未被注意到。
进一步:
您不应在任务中使用 Wait()
。您已经开始使用 async
和 await
,请在您的程序中使用它们。
真正使用 Wait()
的唯一原因是你肯定不能使用异步。尽量避免它。
在 async
时,始终使用后缀 Async
命名您的方法。这表明使用该方法的用户有明确的意图。
我是 C# 中的任务的新手,我遇到了一个我不明白的问题。在下面的代码中,有人可以解释为什么 Unsubscribe 方法末尾的 WriteLine 永远不会被调用。它似乎与前面的 foreach 循环中的 ContinueWith 有关,就好像我评论说它工作正常一样。 谢谢
using System;
using System.Threading;
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Generic;
public class Example
{
public static void Main()
{
Task.Run(() => Unsubscribe());
Console.WriteLine("Method: Main. End");
Console.ReadKey();
}
private static void Unsubscribe()
{
Dictionary<int, string> dicPairsBySubID = new Dictionary<int, string>();
dicPairsBySubID.Add(1, "A");
dicPairsBySubID.Add(2, "B");
dicPairsBySubID.Add(3, "C");
dicPairsBySubID.Add(4, "D");
dicPairsBySubID.Add(5, "E");
List<Task> taskList = new List<Task>();
foreach (KeyValuePair<int, string> sub in dicPairsBySubID)
{
int chanID = sub.Key;
Task task = Task.Run(() => SendUnsubscribe(chanID))
.ContinueWith(tsk =>
{
var flattened = tsk.Exception.Flatten();
flattened.Handle(ex =>
{
Console.WriteLine(string.Format("Error unsubscribing pair {0} (chanID = {2})", sub.Value, chanID), ex);
return true;
});
}, TaskContinuationOptions.OnlyOnFaulted);
taskList.Add(task);
}
Task.WhenAll(taskList).Wait();
// WHY DOES THIS NEVER GET RUN?
Console.WriteLine("Method: Unsubscribe. End");
}
private static async Task SendUnsubscribe(int chanID)
{
await SendAsync(chanID);
Console.WriteLine("Method: SendUnsubscribe. Chan ID: " + chanID);
}
private static async Task SendAsync(int chanID)
{
await Task.Run(() => Thread.Sleep(1000));
Console.WriteLine("Method: SendAsync. Chan ID: " + chanID);
}
}
我对此进行了测试。我在 WhenAll
处遇到异常,说任务已取消。这使得所有这些都有意义:
- 正如 Default 的回答所指出的,您正在将
ContinueWith
任务添加到taskList
。 ContinueWith
任务被取消,因为它应该 运行OnlyOnFaulted
,并且没有发生异常。- 由于抛出异常(未处理),您的
Unsubscribe
的其余部分不是 运行。
您可以采取以下措施来改善这一点:
不要仅对 运行 异步方法使用 Task.Run。您可以这样做:
taskList.Add(SendUnsubscribe(chanID));
在您的
SendUnsubscribe
方法中使用try
/catch
,而不是在任务中使用ContinueWith
。等待
WhenAll
而不是使用Wait()
:await Task.WhenAll(taskList);
如果您认为 SendUnsubscribe
无法处理任何可能发生的异常,您总是可以将 await Task.WhenAll(taskList);
包装在 try
/catch
块中。
你的任务抛出一个你没有捕捉到的异常,因为:
ContinueWith
也是 return 一个任务(然后放在taskList
中)- 您已声明当前一个任务出现故障时(由于
TaskContinuationOptions.OnlyOnFaulted
),该任务仅应 运行 - 你给出的例子没有进入故障状态
- 当 ContinueWith 任务由于这些选项而不是 运行 时 enters the state
Cancelled
- 等待取消的任务抛出异常
- 在您的 Main 方法中,
Task.Run(() => Unsubscribe())
调用的 return 值未被await
编辑,因此从未被注意到。
进一步:
您不应在任务中使用
Wait()
。您已经开始使用async
和await
,请在您的程序中使用它们。 真正使用Wait()
的唯一原因是你肯定不能使用异步。尽量避免它。在
async
时,始终使用后缀Async
命名您的方法。这表明使用该方法的用户有明确的意图。