不了解 C# 中任务的行为

Don't understand behavior of Tasks in C#

我希望有人能帮助我理解一些看似简单的代码。显然,我错过了一些东西。关于这段代码的行为,有两点我不明白:

  1. 如果我让代码 运行,'End Delay' 的最后一个 Debug.WriteLine 永远不会被写入。这是为什么?

  2. 如果我在 Main() 的 Task.WaitAll() 行上放置一个断点,我看到 delayTask 的状态为 WaitingToRun 并且 'Begin Delay' Debug.WriteLine in DoDelay() 不会发生,无论我等多久。但是,一旦我在 Task.WaitAll() 行上按 F10,我就会看到 'Begin Delay' 出现在输出中。为什么 Task.WaitAll() 行上的断点似乎甚至会阻止 开始 任务?无论我使用 Task.WaitAll(delayTask) 还是 await delayTask.

    ,此行为都不会改变

我运行正在使用 .NET Framework 4.6.2,不幸的是,我别无选择。

感谢发帖的每一个人。我有上面第一个问题的答案 - 我将 'async void DoDelay()' 更改为 'async Task DoDelay()'。但是我仍然对第二个问题中的行为没有任何解释。

更新:正如 JonasH 向我指出的那样,在 VS 中处于中断模式时所有任务都会停止。

对于任何感兴趣的人,这篇文章对理解正在发生的事情有很大帮助:https://www.pluralsight.com/guides/understand-control-flow-async-await

static void Main(string[] args) 
{
    Debug.WriteLine($"Main-A: {DateTime.Now:mm:ss.fff}");
    Task delayTask = Task.Run(() => DoDelay(10000));
    Debug.WriteLine($"Main-B: {DateTime.Now:mm:ss.fff}");
    Task.WaitAll(delayTask);
}

static async void DoDelay(int delayMs) 
{
    Debug.WriteLine($"Begin Delay: {DateTime.Now:mm:ss.fff}");
    await Task.Delay(delayMs);
    Debug.WriteLine($"End Delay: {DateTime.Now:mm:ss.fff}");
}

一些注意事项:避免使用 async void 因为你必须 await 才能得到结果,所以我将其更改为 async Task

您必须使用delayTask.Wait();等待完成任务。

警告delayTask.Wait() 是不好的做法,请改用 await delayTask;

使用这个代替你的代码我做了一些修改:

static void Main(string[] args) {

  Debug.WriteLine($"Main-A: {DateTime.Now:mm:ss.fff}");
  Task delayTask = Task.Run(async () => await DoDelay(10000));
  Debug.WriteLine($"Main-B: {DateTime.Now:mm:ss.fff}");
  delayTask.Wait();
}

static async Task DoDelay(int delayMs)
{
  Debug.WriteLine($"Begin Delay: {DateTime.Now:mm:ss.fff}");
  await Task.Delay(delayMs);
  Debug.WriteLine($"End Delay: {DateTime.Now:mm:ss.fff}");
}

你也可以使用

static async Task Main(string[] args) 

并删除 Task.Run(async () => await DoDelay(10000)); 并使用 await DoDelay(10000)await delayTask; 但它可能无法正常工作 .NET Framework 4.6.2

Task delayTask = Task.Run(() => DoDelay(10000));

这将在后台线程上调用方法 DoDelay(10000),并且 return 是在方法 return 时完成的任务。但是,DoDelay 将在第一个 await 语句处 return。该方法的其余部分将在稍后 运行,但没有任何东西会等待它发生。

要解决此问题,您应该将方法 return 设为任务,即 async Task DoDelay(int delayMs)。这不会影响方法 returns 的时间,但是 returned 任务让您等待整个方法完成。这可以通过使用 async main 方法变得更简单:

static async Task Main(string[] args) {
    Debug.WriteLine($"Main-A: {DateTime.Now:mm:ss.fff}");
    var delayTask = DoDelay(10000);
    Debug.WriteLine($"Main-B: {DateTime.Now:mm:ss.fff}");
    await delayTask;
    Debug.WriteLine($"Main-C: {DateTime.Now:mm:ss.fff}");
}

static async Task DoDelay(int delayMs) {
    Debug.WriteLine($"Begin Delay: {DateTime.Now:mm:ss.fff}");
    await Task.Delay(delayMs);
    Debug.WriteLine($"End Delay: {DateTime.Now:mm:ss.fff}");
}

仅在绝对必要时使用 async void,即 UI 中的事件处理程序,然后确保您正在处理任何等待任务的失败。在绝大多数情况下,使用 async Taskasync Task<T>

本例中 async/await 模式的正确实现如下面的代码

static class Program
{
    static async Task Main(string[] args)
    {
        Console.WriteLine($"Main-A: {DateTime.Now:mm:ss.fff}");
        Task delayTask = Task.Run(async () => { await DoDelay(10000); });
        Task delayTaskSync = Task.Run(() => { DoDelaySync(10000); });
        Console.WriteLine($"Main-B: {DateTime.Now:mm:ss.fff}");
        Task.WaitAll(delayTask, delayTaskSync);
    }

    static async Task DoDelay(int delayMs)
    {
        Console.WriteLine($"Begin Delay: {DateTime.Now:mm:ss.fff}");
        await Task.Delay(delayMs);
        Console.WriteLine($"End Delay: {DateTime.Now:mm:ss.fff}");
    }

    static void DoDelaySync(int delayMs)
    {
        Console.WriteLine($"Begin Delay: {DateTime.Now:mm:ss.fff}");
        Task.Delay(delayMs).Wait();
        Console.WriteLine($"End Delay: {DateTime.Now:mm:ss.fff}");
    }
}

在这里您可以看到同时具有异步函数和同步函数的实现。 Task.Run() 运行 是一个并行任务,在该任务中您可以使用更多任务 async/await 或使用同步函数。