C#异步/等待控制台应用程序中的奇怪行为

C#异步/等待控制台应用程序中的奇怪行为

问题描述:

我构建了一些异步/等待演示控制台应用程序,并得到了奇怪的结果。代码:

I built some async/await demo console app and get strange result. Code:

class Program
{
    public static void BeginLongIO(Action act)
    {
        Console.WriteLine("In BeginLongIO start... {0} {1}", (DateTime.Now.Ticks - ticks) / TimeSpan.TicksPerMillisecond, Thread.CurrentThread.ManagedThreadId);
        Thread.Sleep(1000);
        act();
        Console.WriteLine("In BeginLongIO end... \t{0} {1}", (DateTime.Now.Ticks - ticks) / TimeSpan.TicksPerMillisecond, Thread.CurrentThread.ManagedThreadId);
    }

    public static Int32 EndLongIO()
    {
        Console.WriteLine("In EndLongIO start... \t{0} {1}", (DateTime.Now.Ticks - ticks) / TimeSpan.TicksPerMillisecond, Thread.CurrentThread.ManagedThreadId);
        Thread.Sleep(500);
        Console.WriteLine("In EndLongIO end... \t{0} {1}", (DateTime.Now.Ticks - ticks) / TimeSpan.TicksPerMillisecond, Thread.CurrentThread.ManagedThreadId);
        return 42;
    }

    public static Task<Int32> LongIOAsync()
    {
        Console.WriteLine("In LongIOAsync start... {0} {1}", (DateTime.Now.Ticks - ticks) / TimeSpan.TicksPerMillisecond, Thread.CurrentThread.ManagedThreadId);
        var tcs = new TaskCompletionSource<Int32>();
        BeginLongIO(() =>
        {
            try { tcs.TrySetResult(EndLongIO()); }
            catch (Exception exc) { tcs.TrySetException(exc); }
        });
        Console.WriteLine("In LongIOAsync end... \t{0} {1}", (DateTime.Now.Ticks - ticks) / TimeSpan.TicksPerMillisecond, Thread.CurrentThread.ManagedThreadId);
        return tcs.Task;
    }

    public async static Task<Int32> DoAsync()
    {
        Console.WriteLine("In DoAsync start... \t{0} {1}", (DateTime.Now.Ticks - ticks) / TimeSpan.TicksPerMillisecond, Thread.CurrentThread.ManagedThreadId);
        var res = await LongIOAsync();
        Thread.Sleep(1000);
        Console.WriteLine("In DoAsync end... \t{0} {1}", (DateTime.Now.Ticks - ticks) / TimeSpan.TicksPerMillisecond, Thread.CurrentThread.ManagedThreadId);
        return res;
    }

    static void Main(String[] args)
    {
        ticks = DateTime.Now.Ticks;
        Console.WriteLine("In Main start... \t{0} {1}", (DateTime.Now.Ticks - ticks) / TimeSpan.TicksPerMillisecond, Thread.CurrentThread.ManagedThreadId);
        DoAsync();
        Console.WriteLine("In Main exec... \t{0} {1}", (DateTime.Now.Ticks - ticks) / TimeSpan.TicksPerMillisecond, Thread.CurrentThread.ManagedThreadId);
        Thread.Sleep(3000);
        Console.WriteLine("In Main end... \t\t{0} {1}", (DateTime.Now.Ticks - ticks) / TimeSpan.TicksPerMillisecond, Thread.CurrentThread.ManagedThreadId);
    }

    private static Int64 ticks;
}

结果如下:

也许我不完全了解等待的确切原因。我认为如果要等待执行,那么执行将返回到调用方方法,等待任务在另一个线程中运行。在我的示例中,所有操作都在一个线程中执行,并且执行不会在await关键字之后返回到调用方方法。
真相在哪里?

Maybe I do not fully understand what exactly makes await. I thought if the execution comes to await then the execution returns to the caller method and task for awaiting runs in another thread. In my example all operations execute in one thread and the execution doesn't returns to the caller method after await keyword. Where is the truth?

异步等待不是这样有效。

将方法标记为 async 不会创建任何后台线程。当您调用 async 方法时,它会同步运行直到异步点,然后才返回到调用方。

Marking a method as async doesn't create any background threads. When you call an async method it runs synchronously until an asynchronous point and only then returns to the caller.

异步点是当您 await 尚未完成的任务时。完成后,该方法的其余部分将按计划执行。此任务应该表示实际的异步操作(例如I / O或 Task.Delay )。

That asynchronous point is when you await a task that haven't completed yet. When it does complete the rest of the method is scheduled to be executed. This task should represent an actual asynchronous operation (like I/O, or Task.Delay).

在您的代码没有异步点,也没有返回调用线程的点。线程变得越来越深,并在 Thread.Sleep 上阻塞,直到这些方法完成并返回 DoAsync

In your code there is no asynchronous point, there's no point in which the calling thread is returned. The thread just goes deeper and deeper and blocks on Thread.Sleep until these methods are completed and DoAsync returns.

以这个简单示例为例:

public static void Main()
{
    MainAsync().Wait();
}

public async Task MainAsync()
{
    // calling thread
    await Task.Delay(1000);
    // different ThreadPool thread
}

这里有一个实际的异步点( Task.Delay ),调用线程返回到 Main ,然后在任务上同步阻止。一秒钟后, Task.Delay 任务完成,该方法的其余部分在另一个 ThreadPool 线程上执行。

Here we have an actual asynchronous point (Task.Delay) the calling thread returns to Main and then blocks synchronously on the task. After a second the Task.Delay task is completed and the rest of the method is executed on a different ThreadPool thread.

如果不是 Task.Delay ,我们会使用 Thread.Sleep ,那么它们都将在同一调用线程上运行。

If instead of Task.Delay we would have used Thread.Sleep then it will all run on the same calling thread.