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

C#异步Task执行Directory.Exists时未启动问题求助

Troubleshooting Stuck Tasks in Async Network Path Reachability Check

Let’s tackle this confusing issue where your PingHelper tasks get stuck in a queued state on a few machines. First, let’s break down the code’s potential flaws and the likely root causes, then walk through actionable troubleshooting steps.

First: Code Logic & Thread Safety Red Flags

Looking at your PingHelper class, the biggest red flag is unprotected static state with no thread synchronization. Let’s break that down:

  • RealTask and IsAvailable are static fields accessed by multiple threads (since this is a static class) without locks or atomic operations. This creates race conditions that can lead to tasks being orphaned or never properly tracked.
    • For example: Thread A checks RealTask == null and starts to create a task, but before it assigns RealTask, Thread B also checks RealTask == null and creates another task. Now Thread A overwrites RealTask with its own task, leaving Thread B’s task running untracked—this could be one reason tasks appear stuck in queue (or actually running but unmonitored).
  • Your logic for handling completed tasks is off: When RealTask.IsCompleted is true, you set RealTask = null but immediately set IsAvailable = false. That ignores the actual result of the directory check entirely, which might not align with your intended behavior.
  • The timeout logic seems inverted: If dummyTask wins the Task.WhenAny, you set IsAvailable = true—but that means the directory check timed out, which should probably indicate the path is unavailable, not available. That’s a logic bug that might be masking underlying issues.

Why Tasks Get Stuck in Queued State (Even With Thread Pool Set to 1024)

Even if your thread pool max is 1024, here’s why tasks might not start:

1. Thread Pool Starvation From Blocking Operations

Directory.Exists on a network share is a synchronous blocking call—it ties up a thread pool thread until the SMB request completes (which can take seconds if the network is flaky or the share is unresponsive).

By default, the .NET thread pool has a minimum number of worker threads (based on CPU cores, e.g., 2 per core) and only creates new threads every ~500ms if the minimum is exhausted. If you have multiple Check calls triggering these blocking operations, they’ll hog the initial pool of threads. Even if the max is 1024, the thread pool won’t spin up new threads immediately—so new Task.Run calls will queue up until threads become available or the pool creates new ones.

2. Race Conditions Causing Orphaned Tasks

As mentioned earlier, without synchronization, multiple threads can create overlapping tasks. Some of these tasks might be left running in the background (untracked by RealTask), consuming thread pool threads without your knowledge. Over time, this can eat into your thread pool capacity, leading to new tasks being queued.

3. Machine-Specific Configuration Differences

Since this only happens on a few machines, check these system-level settings:

  • Thread pool minimum thread count: If those machines have a lower minimum worker thread count (e.g., modified via ThreadPool.SetMinThreads), the pool will take longer to scale up when blocking tasks hit.
  • Network stack issues: The underlying problem (flaky network cards) might be causing longer SMB timeouts on those machines, making the blocking Directory.Exists calls hold threads hostage longer.
  • Antivirus/Endpoint Security: Some security tools intercept network file system calls, adding delays or blocking threads unexpectedly on specific machines.

Troubleshooting Steps to Validate

  1. Add Thread Synchronization
    Wrap access to RealTask and IsAvailable in a lock to eliminate race conditions. For example:

    private static readonly object _lockObj = new object();
    private static Task<bool> RealTask { get; set; } // Track result explicitly
    public static bool IsAvailable { get; private set; }
    
    public static async Task Check(string location, int timeout)
    {
        lock (_lockObj)
        {
            if (RealTask != null)
            {
                if (RealTask.IsCompleted)
                {
                    IsAvailable = RealTask.Result;
                    RealTask = null;
                }
                else
                {
                    IsAvailable = false;
                    return;
                }
            }
        }
    
        DirectoryInfo directoryInfo = new DirectoryInfo(location);
        var checkTask = Task.Run(() => directoryInfo.Exists);
        var delayTask = Task.Delay(timeout);
    
        lock (_lockObj)
        {
            RealTask = checkTask;
        }
    
        var completedTask = await Task.WhenAny(checkTask, delayTask);
        lock (_lockObj)
        {
            if (completedTask == delayTask)
            {
                // Timeout: mark as unavailable, discard the stuck task
                IsAvailable = false;
                RealTask = null;
            }
            else
            {
                // Check completed: use the actual result
                IsAvailable = checkTask.Result;
                RealTask = null;
            }
        }
    }
    

    This ensures only one task is tracked at a time, and race conditions are eliminated.

  2. Avoid Blocking Thread Pool Threads
    Instead of Task.Run for a blocking call, use Task.Factory.StartNew with TaskCreationOptions.LongRunning—this tells the thread pool to create a dedicated thread (not from the worker pool) for the blocking operation, avoiding pool starvation:

    var checkTask = Task.Factory.StartNew(() => directoryInfo.Exists, TaskCreationOptions.LongRunning);
    
  3. Inspect Thread Pool State on Affected Machines
    During debugging, use ThreadPool.GetAvailableThreads(out int workerThreads, out int completionPortThreads) to check how many worker threads are available. If available threads are near zero, that confirms thread pool starvation from blocking tasks.

  4. Test Network Path Timeouts Directly
    On the problematic machines, run a manual dir \\server\share command to see how long it takes to return (or time out). If these commands hang for extended periods, that’s the root cause of the thread blocking—and you’ll need to address the network/nic issues alongside your code fixes.

Final Notes

The core issues here are thread safety gaps in your static state management, and blocking operations tying up thread pool resources. Fixing the synchronization and adjusting how you handle blocking tasks should resolve the queued task problem on those machines.

内容的提问来源于stack exchange,提问作者Andrei Don

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 08:56:51