简体   繁体   中英

C# and Tasks - UI Thread Hang - Pre-Async/Await keywords

I'm trying to understand what the correct code to grab a set of data asynchronously when I do not have access to the client lib I am using to retrieve the data. I specify an endpoint and a date range and I'm supposed to retrieve a list of playlists. What I have now never comes back after the Start() call. Note: this is running in a WinForm. I am trying to better understand Tasks and don't just want to jump to awaits or a BackgroundWorker. I know I'm getting lost somewhere.

    private void GoButtonClick(object sender, EventArgs e)
    {
        string baseUrl = "http://someserver/api";
        var startDateTime = this._startDateTimePicker.Value;
        var endDateTime = this._endDateTimePicker.Value;
        _getPlaylistsFunc = delegate()
            {
                var client = new PlaylistExportClient(baseUrl);
                return client.GetPlaylistsByDateRange(startDateTime, endDateTime).ToList();
            };
        var task = new Task<List<Playlist>>(_getPlaylistsFunc);
        task.ContinueWith((t) => DisplayPlaylists(t.Result));
        task.Start();
    }

    private void DisplayPlaylists(List<Playlist> playlists)
    {
        _queueDataGridView.DataSource = playlists;
    }

UPDATE I made these changes but now the application seems to hang the UI thread.

    private void GoButtonClick(object sender, EventArgs e)
    {
        string baseUrl = "http://someserver/api";
        var startDateTime = this._startDateTimePicker.Value;
        var endDateTime = this._endDateTimePicker.Value;
        var token = Task.Factory.CancellationToken;

        var context = TaskScheduler.FromCurrentSynchronizationContext();
        Task.Factory.StartNew(() =>
            {
                var client = new PlaylistExportClient(baseUrl);
                _queueDataGridView.DataSource = client.GetPlaylistsByDateRange(startDateTime, endDateTime).ToList();

            },token,TaskCreationOptions.None,context);

    }

I recommend you use the Task-based Asynchronous Pattern. It's quite straightforward:

private async void GoButtonClick(object sender, EventArgs e)
{
    string baseUrl = "http://someserver/api";
    var startDateTime = this._startDateTimePicker.Value;
    var endDateTime = this._endDateTimePicker.Value;
    var playlists = await Task.Run(() =>
    {
        var client = new PlaylistExportClient(baseUrl);
        return client.GetPlaylistsByDateRange(startDateTime, endDateTime).ToList();
    });
    _queueDataGridView.DataSource = playlists;
}

Note that this will block a threadpool thread; if you can modify the library to have a GetPlaylistsByDateRangeAsync method, you can make this more efficient.

Edit: If you're stuck on .NET 4.0, you can install Microsoft.Bcl.Async to get full async / await capabilities. If - for some inexplicable reason - you still can't use async / await , then you can do it like this:

private void GoButtonClick(object sender, EventArgs e)
{
    string baseUrl = "http://someserver/api";
    var startDateTime = this._startDateTimePicker.Value;
    var endDateTime = this._endDateTimePicker.Value;
    var context = TaskScheduler.FromCurrentSynchronizationContext();
    Task.Run(() =>
    {
        var client = new PlaylistExportClient(baseUrl);
        return client.GetPlaylistsByDateRange(startDateTime, endDateTime).ToList();
    }).ContinueWith(t =>
    {
        _queueDataGridView.DataSource = t.Result;
    }, context);
}

However, note that your error handling is more complex with this approach.

It looks like you're assigning to a property of a UI control in a background thread. That's usually bad news. WPF usually throws an exception when you do that, not sure about WinForms.

Capture the data in the background thread, but switch back to the main UI thread before assigning it to a UI control. Try posting the data to the UI thread using something like

    var uiSync = SynchronizationContext.Current;
    Task.Factory.StartNew(() =>
        {
            var client = new PlaylistExportClient(baseUrl);
            var list = client.GetPlaylistsByDateRange(...).ToList();
            uiSync.Post(() => _queueDataGridView.DataSource = list, null);
        },token,TaskCreationOptions.None,context);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM