【问题标题】:Why does await never return?为什么等待永远不会返回?
【发布时间】:2017-08-02 19:25:16
【问题描述】:

我看到了一个似乎永远不会返回的等待。这是示例代码:

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 被调用,它将状态设置为“正在运行...”,然后调用 StartProcessing。五秒钟过去了,但我从未看到状态设置为“完成!”

如果我调用 OnStop,则任务被取消,我看到“完成!”状态。

我猜我正在创建一个任务以及由 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>

【问题讨论】:

  • 最佳做法是使用async Task 而不是async void。
  • 您没有启动任务。请使用 Task.Run()
  • @StephenWilson 虽然这在 99% 的情况下都是更好的做法,但您需要在 Event Handlers 上使用 async void,这就是 OP 正在使用的
  • @CamiloTerevinto using async void for 对事件处理程序有效,但 async Task 可以/应该用于 StartProcessing() 方法。

标签: c# async-await task-parallel-library


【解决方案1】:

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

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

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

【讨论】:

  • 如果我直接用 Task.Delay 替换 Thread.Sleep ,它会创建另一个任务,以便主任务立即完成,我看到“完成!”一旦我点击开始。如果我删除所有多余的绒毛并将其替换为 await Task.Delay(5000, _cancellationTokenSource.Token);效果更好!
  • @imekon 当然,这就是你应该使用 Task.Delay 的方式,而不需要额外的任务来包装
【解决方案2】:

您希望避免在方法上使用async void。更新 StartProcessing 以返回 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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-16
    • 2015-09-21
    • 2021-11-10
    • 1970-01-01
    • 1970-01-01
    • 2016-02-08
    相关资源
    最近更新 更多