You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Task.WaitAll在任务故障执行时持续阻塞问题咨询(含自定义RunAsync代码)

Why Task.WaitAll Blocks When Tasks Fail (or Are Canceled)

Alright, let's break down what's going wrong here and fix your custom RunAsync method step by step.

The Core Problems in Your Current Code

Your RunAsync implementation has critical gaps in handling cancellation and task completion:

  • You're passing the CancellationToken but not actually using it: The token is just sent as state to QueueUserWorkItem—there's no logic to cancel the task if the token is triggered. This means even if the token is canceled, your task will either keep running forever or get stuck in an incomplete state.
  • No early cancellation check: If the token is already canceled when you call RunAsync, you still queue the work item instead of immediately returning a canceled task.
  • Uncompleted tasks stay pending: If the token is canceled mid-execution, your TaskCompletionSource never gets told to mark the task as canceled. Task.WaitAll waits for all tasks to reach a completed state (success, failure, or cancellation)—so stuck pending tasks will make it block indefinitely.

Fixed RunAsync<T> Implementation

Here's a corrected version that properly handles cancellation and ensures tasks always complete:

public static Task<T> RunAsync<T>(Func<T> function, CancellationToken token)
{
    if (function == null)
        throw new ArgumentNullException(nameof(function));
    
    // Bail early if the token is already canceled—no need to queue work
    if (token.IsCancellationRequested)
        return Task.FromCanceled<T>(token);
    
    var tcs = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
    
    // Register a callback to cancel the task if the token is triggered
    var cancellationRegistration = token.Register(() => tcs.TrySetCanceled(token));
    
    ThreadPool.QueueUserWorkItem(_ =>
    {
        try
        {
            // Check for cancellation again before running the function (in case token was canceled after queueing)
            token.ThrowIfCancellationRequested();
            
            var result = function();
            tcs.TrySetResult(result);
        }
        catch (OperationCanceledException)
        {
            // Expected when the token is canceled—mark the task as canceled
            tcs.TrySetCanceled(token);
        }
        catch (Exception ex)
        {
            // Catch any other exceptions and mark the task as faulted
            tcs.TrySetException(ex);
        }
        finally
        {
            // Clean up the cancellation registration to avoid memory leaks
            cancellationRegistration.Dispose();
        }
    }, null); // No need to pass the token as state anymore
    
    return tcs.Task;
}

What Changed & Why

  • Early cancellation check: Avoids wasting thread pool resources if the token is already canceled when the method is called.
  • Cancellation callback: Ensures that if the token is canceled while the task is waiting to run or executing, the TaskCompletionSource is updated to mark the task as canceled—this completes the task so WaitAll doesn't hang.
  • Cleanup in finally: Disposing the cancellation registration prevents memory leaks from orphaned callbacks.
  • Explicit cancellation check: Catches cases where the token was canceled after the work item was queued but before it started running.
  • Proper exception handling: Maps cancellation exceptions to the task's canceled state, and other exceptions to faulted state—both of these count as completed states, so WaitAll will proceed.

Why Your Original Code Caused WaitAll to Block

In your original code, if the token was canceled, the task would never transition to a completed state (it would stay in WaitingForActivation forever). Task.WaitAll waits for every task to finish—success, fail, or get canceled—so any stuck pending tasks will make it block indefinitely. Even if some tasks failed, if others were stuck pending, WaitAll would still hang.

Better Timer Monitoring (Avoid Blocking)

Instead of using Task.WaitAll (which blocks the timer thread), consider using Task.WhenAll with await for a non-blocking approach. Note that System.Timers.Timer swallows exceptions from async callbacks by default, so prefer System.Threading.Timer:

// Example async timer callback
private async void TimerCallback(object state)
{
    var taskList = (List<Task>)state;
    
    try
    {
        // Asynchronously wait for all tasks to complete—no blocking
        await Task.WhenAll(taskList);
        Console.WriteLine("All tasks completed successfully!");
    }
    catch (AggregateException ae)
    {
        // Handle exceptions from faulted tasks
        foreach (var ex in ae.InnerExceptions)
        {
            Console.WriteLine($"Task failed: {ex.Message}");
        }
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("One or more tasks were canceled.");
    }
    
    // Clean up completed tasks from the list
    taskList.RemoveAll(t => t.IsCompleted);
}

内容的提问来源于stack exchange,提问作者The Overrider

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 11:42:36