为什么 await 从不 return?

Why does await never return?

我看到了一个似乎从未出现过的等待 return。这是示例代码:

public partial class MainWindow : Window, INotifyPropertyChanged
{
    private string _status;
    private CancellationTokenSource _cancellationTokenSource;

    public MainWindow()
    {
        InitializeComponent();

        _status = "Ready";

        DataContext = this;
    }

    public string Status
    {
        get { return _status; }
        set
        {
            _status = value;
            OnPropertyChanged(nameof(Status));
        }
    }

    private void OnStart(object sender, RoutedEventArgs e)
    {
        Status = "Running...";

        _cancellationTokenSource = new CancellationTokenSource();

        StartProcessing();
    }

    private void OnStop(object sender, RoutedEventArgs e)
    {
        _cancellationTokenSource.Cancel();
    }

    private async void StartProcessing()
    {
        try
        {
            await new Task(() =>
            {
                Thread.Sleep(5000);
            }, _cancellationTokenSource.Token);
        }
        catch (TaskCanceledException e)
        {
            Debug.WriteLine($"Expected: {e.Message}");
        }

        Status = "Done!";
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

调用 OnStart,将状态设置为 "Running...",然后调用 StartProcessing。五秒钟过去了,但是我从未看到状态设置为 "Done!"

如果我调用 OnStop,任务将被取消,我会看到 "Done!" 状态。

我想我正在创建一个任务以及 async/await 创建的任务,但它挂起或死锁?

这是 WPF XAML 代码:

<Window x:Class="CancellationSample.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Title="Cancellation Test" Height="350" Width="525">
<DockPanel LastChildFill="True">
    <StackPanel DockPanel.Dock="Top">
        <Button Width="70" Margin="5" Click="OnStart">Start</Button>
        <Button Width="70" Margin="5" Click="OnStop">Stop</Button>
    </StackPanel>
    <StatusBar DockPanel.Dock="Bottom">
        <StatusBarItem>
            <TextBlock Text="{Binding Status}"/>
        </StatusBarItem>
    </StatusBar>
    <Grid></Grid>
</DockPanel>
</Window>

您正在创建一个 new Task 但没有启动它,所以它永远不会完成。而是使用 Task.Runawait

await Task.Run(() => { });

也考虑使用 Task.Delay 而不是 Thread.Sleep 这样你就不会阻塞当前线程,

您想避免在方法上使用 async void。将 StartProcessing 更新为 return Task 并且您还应该使用 Task.Delay 而不是 Thread.Sleep

private async Task StartProcessing() {
    try {
        await Task.Delay(5000, _cancellationTokenSource.Token);
    } catch (TaskCanceledException e) {
        Debug.WriteLine($"Expected: {e.Message}");
    }
    Status = "Done!";
}

接下来,如果 OnStart 实际上是一个事件处理程序,那么它是允许 async void 的唯一例外。将 OnStart 更新为异步,然后等待现在可等待的 StartProcessing

private async void OnStart(object sender, RoutedEventArgs e) {
    Status = "Running...";

    _cancellationTokenSource = new CancellationTokenSource();

    await StartProcessing();
}

最后我建议阅读

Async/Await - Best Practices in Asynchronous Programming By Stephen Cleary

更好地了解如何使用 async/await