gpt4 book ai didi

c# - 立即捕获异常不适用于 Task.WhenAll

转载 作者:行者123 更新时间:2023-12-04 01:13:37 31 4
gpt4 key购买 nike

我有一个类的两个实例,它们创建一个 UDP 套接字以从 UDP 客户端接收数据。如果其中一个实例抛出异常,我想立即在更高层处理它。在我的程序中,它们以 await Task.WhenAll(recv1.StartAsync(), recv2.StartAsync) 开始。然而,这会在抛出第一个异常之前等待所有任务完成。关于如何解决此问题的任何想法?

static async Task Main(string[] args)
{
var udpReceiver1 = new UdpReceiver(localEndpoint1);
var udpReceiver2 = new UdpReceiver(localEndpoint2);

var cts = new CancellationTokenSource();

try
{
await Task.WhenAll(udpReceiver1.StartAsync(cts.Token), udpReceiver2.StartAsync(cts.Token));
}
catch (Exception e)
{
// Handle Exception...

cts.Cancel();
}
}

class UdpReceiver
{
public UdpReceiver(IPEndPoint endpoint)
{
udpClient = new UdpClient(endpoint);
}

public async Task StartAsync(CancellationToken cancellationToken)
{
try
{
while (!cancellationToken.IsCancellationRequested)
{
var result = await ReceiveAsync(cancellationToken);
var message = Encoding.UTF8.GetString(result.Buffer);
Trace.WriteLine($"UdpClient1 received message:{Encoding.UTF8.GetString(result.Buffer)}");

// throw new Exception("UdpClient1 raising exception");
}
}
}

private async Task<UdpReceiveResult> ReceiveAsync(CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<UdpReceiveResult>();
using (cancellationToken.Register(() => tcs.TrySetCanceled(), false))
{
var task = udpClient.ReceiveAsync();

var completedTask = await Task.WhenAny(task, tcs.Task);

var result = await completedTask.ConfigureAwait(false);

return result;
}
}

private UdpClient udpClient;
}

更新 1:Task.WhenAny 将是一个可行的解决方案。谢谢@CamiloTerevinto

try
{
await await Task.WhenAny(udpReceiver1.StartAsync(cts.Token), udpReceiver2.StartAsync(cts.Token));
}
catch (Exception e)
{
// Handle Exception...

cts.Cancel();
}

更新 2:为了对所有任务进行更细粒度的异常处理,我会使用我自己改编的 Task.WhenAll 实现,由 @Servy 提出。

最佳答案

该行为与框架 WhenAll 实现有很大不同,您可能最好只编写自己的改编版本,幸运的是,实现起来并不是特别困难。只需为每个任务附加一个延续,如果任何任务被取消或出错,生成的任务也会执行相同的操作,如果成功则存储结果,如果最后一个任务是成功的,则使用所有存储的结果完成任务.

public static Task<IEnumerable<TResult>> WhenAll<TResult>(IEnumerable<Task<TResult>> tasks)
{
var listOfTasks = tasks.ToList();
if (listOfTasks.Count == 0)
{
return Task.FromResult(Enumerable.Empty<TResult>());
}
var tcs = new TaskCompletionSource<IEnumerable<TResult>>();
var results = new TResult[listOfTasks.Count];
int completedTasks = 0;
for (int i = 0; i < listOfTasks.Count; i++)
{
int taskIndex = i;
Task<TResult> task = listOfTasks[i];
task.ContinueWith(_ =>
{
if (task.IsCanceled)
tcs.TrySetCanceled();
else if (task.IsFaulted)
tcs.TrySetException(task.Exception.InnerExceptions);
else
{
results[taskIndex] = task.Result;
if (Interlocked.Increment(ref completedTasks) == listOfTasks.Count)
{
tcs.TrySetResult(results);
}
}
});
}
return tcs.Task;
}

与许多基于任务的通用操作一样,您还需要一个没有结果的版本,如果您不想处理显着的开销,您真的需要复制粘贴基于结果的方法,但是所有的结果都被撕掉了,这并不难,只是不够优雅。将所有这些任务转换为有结果的任务也可行,但对于像这样的操作,开销可能有问题。

public static Task WhenAll(IEnumerable<Task> tasks)
{
var listOfTasks = tasks.ToList();
if (listOfTasks.Count == 0)
{
return Task.CompletedTask;
}
var tcs = new TaskCompletionSource<bool>();
int completedTasks = 0;
for (int i = 0; i < listOfTasks.Count; i++)
{
int taskIndex = i;
Task task = listOfTasks[i];
task.ContinueWith(_ =>
{
if (task.IsCanceled)
tcs.TrySetCanceled();
else if (task.IsFaulted)
tcs.TrySetException(task.Exception.InnerExceptions);
else
{
if (Interlocked.Increment(ref completedTasks) == listOfTasks.Count)
{
tcs.TrySetResult(true);
}
}
});
}
return tcs.Task;
}

关于c# - 立即捕获异常不适用于 Task.WhenAll,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64053239/

31 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com