【问题标题】:Returning from a task without blocking UI thread从任务返回而不阻塞 UI 线程
【发布时间】:2014-12-14 17:52:17
【问题描述】:

我有一个返回数据表的方法。我需要所有的 sql 东西在一个线程中运行,然后能够在不阻塞 UI 线程的情况下传回一个数据表。据我了解,当您调用 Task.Result 时,它会阻塞 UI 线程,直到任务完成。我将如何解决这个问题。我阅读了有关使用 await 和 async 的信息,但我还没有完全弄清楚如何在任务中使用它。

public static DataTable LaunchLocationMasterListReport(ObservableCollection<string> BuiltConditionsList, ObservableCollection<string> BuiltSortList, ObservableCollection<ListBoxCheckBoxItemModel> ColumnsForReport,
    bool LocationNotesCheckBox, ref string reportQuery, ref string reportQueryforSave, ref string reportView, ref string queryCondtions)
{
    queryCondtions = BuildConditionAndSorts(queryCondtions, BuiltConditionsList, BuiltSortList);
    reportQueryforSave = "SELECT * FROM LocationMasterReportView";
    reportView = "LocationMasterReportView";
    reportQuery = "SELECT * FROM LocationMasterReportView " + queryCondtions;

    return LaunchReport(reportQuery, ColumnsForReport).Result;
}

async private static Task<DataTable> LaunchReport(string reportQuery, ObservableCollection<ListBoxCheckBoxItemModel> ColumnsForReport)
{
    SqlConnection myConn = new SqlConnection(Settings.Default.UltrapartnerDBConnectionString);
    DataTable dt = new DataTable();

    string rq = reportQuery;

    Task<DataTable> task = Task.Factory.StartNew(() =>
    {
        using (SqlCommand comm = new SqlCommand(rq, myConn))
        {
            myConn.Open();
            dt.Load(comm.ExecuteReader());
            myConn.Close();
        }

        if (dt.Rows.Count == 0)
        {
            MessageBox.Show("Contains No Results");
            return null;
        }

        foreach (ListBoxCheckBoxItemModel lbc in ColumnsForReport)
        {
            if (!lbc.IsSelected)
            {
                dt.Columns.Remove(lbc.Name.ToString());
            }
        }

        return dt;

    }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);

    return await task;
}

【问题讨论】:

  • 在任务中使用asyncawait 很简单:将async 添加到您的方法签名中,将返回类型更改为Task&lt;DataTable&gt;,并将return task.Result 替换为return task。您可能需要将调用此方法的代码重构为async,以便您可以await 得到结果(这会为您提供DataTable 而不是任务)。阅读更多关于该主题的内容——这绝对值得。
  • 不要使用task.Result。这是一个阻塞调用。请改用return await task。 (顺便说一句:您的方法签名将类似于 async public Task&lt;DataTable&gt; Launch
  • 我必须使用 .Result 一些方法来找回 DataTable,对吗?让我用我所拥有的更新代码。

标签: c# wpf multithreading task


【解决方案1】:

我同意在这里使用 async/await 是最好的方法。如前所述,当您等待异步方法时,即使声明的返回类型是 Task,编译器也会将其转换为 T 的隐式返回类型。

问题是所有异步方法都必须返回 void、Task 或 Task。所以一旦你开始使用它们,你必须“冒泡”“async”方法属性,直到你可以阻塞结果或者你的方法可以是 void 或 Task(即你已经消耗了实际的结果)。

请看这个基于 UI 的简单示例:

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private async void Button_Click(object sender, RoutedEventArgs e)
    {
        statusText.Text = "Running";
        statusText.Text = await _ComputeText(true);
        statusText.Text = await _ComputeText(false);
    }

    private static async Task<string> _ComputeText(bool initialTask)
    {
        string result = await Task.Run(() =>
            {
                Thread.Sleep(2000);
                return initialTask ? "Task is done!" : "Idle";
            });

        return result;
    }
}

请注意,按钮事件处理程序“Button_Click”被简单地声明为“void”返回。但我可以这样做,因为我在该方法中使用了异步结果。

在您的情况下,在异步任务完成之前,返回的 DataTable 不可用。因此,您必须将每个方法声明为“异步”,一直到实际执行的任何方法都可以对 DataTable 执行某些操作。即使在那里,该方法也需要声明为异步,但您不会返回 DataTable,因此该方法可以具有“void”或“Task”的返回类型。一个常见的场景是这个方法是一个 UI 事件处理程序,所以“void”在那里应该没问题(并且需要在事件处理程序委托中使用);无论如何,您的代码都没有调用它。但从技术上讲,改用“任务”更正确,所以如果在你的上下文中有效,你应该这样做。

如果没有简洁但完整的示例,很难提供比这更具体的内容。

【讨论】:

  • 感谢您的详细解释...这确实有助于理解这一点
  • .net 4 没有 task.run 有解决方案吗?
  • @blackholeearth0_gmail:使用 .NET 4,您可以将 install the TPL, with async/await 作为单独的库。或者,您可以在 .NET 4 中使用 TaskFactory.StartNew(),但在这种情况下,您必须编写自己的延续代码(即调用 ContinueWith()),而不是能够利用 async/await。
猜你喜欢
  • 1970-01-01
  • 2017-07-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-27
  • 1970-01-01
  • 2013-08-08
相关资源
最近更新 更多