imekon
imekon

Reputation: 1523

Why does await never return?

I'm seeing an await that never seems to return. Here's the sample code:

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));
    }
}

What happens is OnStart gets called, it sets status to "Running...", then calls StartProcessing. Five seconds lapse, however I never see the status get set to "Done!"

If I call OnStop, then the task is cancelled and I see the "Done!" status.

I'm guessing I'm creating a task as well as the task created by async/await but it's hanging or deadlocking?

Here's the WPF XAML code:

<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>

Upvotes: 5

Views: 2257

Answers (2)

Owen Pauling
Owen Pauling

Reputation: 11841

You are creating a new Task but not starting it, so it never finishes. Instead use Task.Run and await on that.

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

Consider also using Task.Delay rather than Thread.Sleep so that you don't block the current thread,

Upvotes: 6

Nkosi
Nkosi

Reputation: 246998

You want to avoid using async void on methods. Update StartProcessing to return Task and also you should use Task.Delay instead of Thread.Sleep

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

Next if OnStart is in fact an event handler it is the one exception where async void is allowed. Update OnStart to be async and then await the now awaitable StartProcessing

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

    _cancellationTokenSource = new CancellationTokenSource();

    await StartProcessing();
}

Finally I would suggest reading

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

to get a better understanding of how to use async/await

Upvotes: 5

Related Questions