【问题标题】:C# and Tasks - UI Thread Hang - Pre-Async/Await keywordsC# 和任务 - UI 线程挂起 - Pre-Async/Await 关键字
【发布时间】:2012-12-21 05:07:43
【问题描述】:

当我无法访问用于检索数据的客户端库时,我试图了解什么是异步获取一组数据的正确代码。我指定了一个端点和一个日期范围,我应该检索一个播放列表列表。在 Start() 调用之后,我现在再也没有回来过。注意:这是在 WinForm 中运行的。我试图更好地理解任务,而不只是想跳到等待或 BackgroundWorker。我知道我迷路了。

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

更新 我进行了这些更改,但现在应用程序似乎挂起了 UI 线程。

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

    }

【问题讨论】:

  • 如果你在GetPlaylistsByDateRange 中放置一个断点,你看到它实际上被调用了吗?
  • 您需要将同步上下文传递给延续,以便它在 UI 线程而不是另一个线程池线程中运行。除此之外,我在这里看不到任何真正的错误。
  • 您的更新在 UI 线程上运行 所有 任务。只有第二个应该在那里运行。

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


【解决方案1】:

我建议您使用基于任务的异步模式。这很简单:

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

注意这会阻塞一个线程池线程;如果您可以将库修改为具有GetPlaylistsByDateRangeAsync 方法,则可以提高效率。

编辑:如果您卡在 .NET 4.0 上,您可以安装 Microsoft.Bcl.Async 以获得完整的 async/await 功能。如果 - 由于某种莫名其妙的原因 - 你仍然不能使用async/await,那么你可以这样做:

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

但是,请注意,使用这种方法您的错误处理更加复杂。

【讨论】:

  • 我明确地试图了解这是如何在异步/等待之前完成的。但也有很好的信息。
  • 我根据问题上指定的async-await 标签给出了这个答案。如果你想要一个预先async 的答案,Task.ContinueWith(加上TaskScheduler.FromCurrentSynchronizationContext)或BackgroundWorker 就足够了。 async 代码比这两个选项都干净。
  • 使用ContinueWith 方法查看代码的更新答案。
  • +1。更新后的代码正是发布者想要的,除了 Task.Run 需要为 Task.Factory.StartNewRun 在 .NET 4.0 中不存在。
  • 是的,.NET 4.0 中没有Task.Run(),即使安装了VS2010 Async CTP (Version 3),它仍然不可用。如果有人坚持使用 .NET 4.0,那么他通常会坚持使用 VS2010,您对 Microsoft.Bcl.Async 的引用是无用的
【解决方案2】:

看起来您正在后台线程中分配给 UI 控件的属性。这通常是坏消息。当您这样做时,WPF 通常会引发异常,不确定 WinForms。

在后台线程中捕获数据,但在将其分配给 UI 控件之前切换回主 UI 线程。尝试使用类似

的方式将数据发布到 UI 线程
    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);

【讨论】:

  • 嗯。我明白——我想。 .Post() 不是我上下文中的方法。
  • 糟糕。好的,你所说的上下文变量实际上是一个调度程序。我将用代码更新我的答案以获取同步上下文。
  • 看起来真的很接近。谢谢。我只是在研究 Post() lambda 参数。现在 IDE 正在报告匿名函数的不正确签名。
  • 仍在锁定我的 WinForm。我所做的唯一更改是将(对象状态)添加到 Post() 调用中。
猜你喜欢
  • 2012-11-28
  • 1970-01-01
  • 1970-01-01
  • 2012-06-11
  • 1970-01-01
  • 1970-01-01
  • 2015-03-16
  • 2021-01-04
  • 1970-01-01
相关资源
最近更新 更多