【问题标题】:Async/Await and Entity Framework SQL Procedure UI Thread LockingAsync/Await 和实体框架 SQL 过程 UI 线程锁定
【发布时间】:2019-01-08 02:31:43
【问题描述】:

我一直在将自己从 BackGroundWorker 过渡到 Async/Await 编程。在大多数情况下,它非常简单。但是,今天我正在使用 Entity Framework 6,但效果并不好。我这里有一个程序,我正在执行大量条目。我想要对进度条的反馈,因为这是一个漫长的过程,由一些冗长的过程组成。

这是后台工作者;不会线程锁定 UI,我正在使用;

private void ButtonMRCUpdateAll(object sender, EventArgs e) {
    cmdMRCUpdateAllExcelSheets.Enabled = false;
    cmdMRCUpdateSingleClient.Enabled = false;
    tsStatusBar.Value = 0;
    tsStatusBar.Visible = true;
    var bw = new BackgroundWorker();
    bw.WorkerReportsProgress = true;
    bw.DoWork += delegate {
        DateTime YearAndMonth = new DateTime(dtpMRC.Value.Year, dtpMRC.Value.Month, 1);
        List<string> List = new List<string>();
        using (wotcDB DB = new wotcDB()) {
            var r = DB.client_main.
                Where(t => t.Active == true).
                OrderBy(t => t.CLIENTCODE).
                Select(t => t.CLIENTCODE);
            List.AddRange(r.ToArray());
        }
        bw.ReportProgress(0, List.Count);
        int PercentComplete = 0;
        foreach (var Client in List) {
            using (wotcDB DB = new wotcDB()) {
                DB.system_MRC(Client, YearAndMonth);
            }
            PercentComplete++;
            bw.ReportProgress(PercentComplete);
        }
    };
    bw.ProgressChanged += delegate (object s, ProgressChangedEventArgs ex) {
        if (ex.UserState != null) { tsStatusBar.Maximum = (int)ex.UserState; }
        tsStatusBar.Value = ex.ProgressPercentage;
    };
    bw.RunWorkerCompleted += delegate {
        MessageBox.Show("Monthly Results and Changes - Task Complete");
        cmdMRCUpdateAllExcelSheets.Enabled = true;
        cmdMRCUpdateSingleClient.Enabled = true;
        tsStatusBar.Visible = false;
        tsStatusBar.Value = 0;
    };
    bw.RunWorkerAsync();
}

这是我尝试使用的 Async/Await。这个线程锁定了用户界面,我不知道为什么。我认为这是实体框架调用,因为我已经将 foreach 与其他非 EF 函数一起使用,并且运行良好。

private async void ButtonMRCUpdateAllExcelSheetsClick(object sender, EventArgs e) {
    await ProcessEntries();
}

private async Task ProcessEntries() {
    cmdMRCUpdateAllExcelSheets.Enabled = false;
    cmdMRCUpdateSingleClient.Enabled = false;
    tsStatusBar.Value = 0;
    tsStatusBar.Visible = true;

    DateTime YearAndMonth = new DateTime(dtpMRC.Value.Year, dtpMRC.Value.Month, 1);
    List<string> List = new List<string>();
    using (wotcDB DB = new wotcDB()) {
        var r = DB.client_main.
            Where(t => t.Active == true).
            OrderBy(t => t.CLIENTCODE).
            Select(t => t.CLIENTCODE);
        List.AddRange(await r.ToArrayAsync());
    }
    tsStatusBar.Maximum = List.Count;

    foreach (var Client in List) {
        await AsyncProcessEntry(Client, YearAndMonth);
        tsStatusBar.Value += 1;
    }

    cmdMRCUpdateAllExcelSheets.Enabled = true;
    cmdMRCUpdateSingleClient.Enabled = true;
    tsStatusBar.Visible = false;
    tsStatusBar.Value = 0;
    MessageBox.Show("Monthly Results and Changes - Task Complete");
}

private Task<string> AsyncProcessEntry(string Client, DateTime? YearAndMonth) {
    var tcs = new TaskCompletionSource<string>();
    using (wotcDB DB = new wotcDB()) {
       DB.system_MRC(Client, YearAndMonth);
    }
    tcs.SetResult("");
    return tcs.Task;
}

我在导致 UI 线程锁定的 Async/Await 中做错了什么?

编辑(system_MRC);

ALTER PROCEDURE [dbo].[system_MRC]
    -- Add the parameters for the stored procedure here
    @CLIENTCODE AS VARCHAR(16),
    @Date AS DATE
AS
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

    ~~ A whole lot of math stuff, then some inserts/updates based on if the entries exist already.

END

【问题讨论】:

  • 您不需要任何代码。 BGW 尤其过时。使用.ToListAsync().ToArrayAsync()等异步执行EF查询,即var list= await r.ToListAsync();
  • 另一个问题是async void 仅适用于事件处理程序。它不能等待。如果要创建不返回任何结果的异步方法,请使用 async Task
  • 这是一个程序,所以我不想得到回报。有没有办法异步运行程序?
  • 是的,使用async Task,而不是async void
  • 当您await 时,默认 行为是尊重同步上下文,然后通过它返回 - 这对您来说意味着“在用户界面线程"; AsyncProcessEntry 在执行异步操作之前是否碰巧做了很多非异步工作?因为如果是这样:那将发生在 UI 线程上。 ConfigureAwait(false) 可能会有所帮助,但是 a:UI 位 需要 在 UI 线程上,并且 b:如果 AsyncProcessEntry 进行重要的非异步工作,则需要将其移出 UI 线程

标签: c# multithreading tsql asynchronous entity-framework-6


【解决方案1】:

我让它工作了。由于我使用的是过程,因此无法使用 .ConfigureAwait(false)

我确实结合了 cmets 的建议,以使读者异步。这是一个小查询,因此不会从中受益匪浅,但是我还有其他查询可以,并且我将把它作为所有查询的标准操作的一部分。

为了让它正常工作,我必须做的是将过程包装在 await Task.Run() 中并以这种方式使用它。这节省了很多代码,并且我设法回到了单个函数。

private async void ButtonMRCUpdateAllExcelSheetsClick(object sender, EventArgs e) {
    cmdMRCUpdateAllExcelSheets.Enabled = false;
    cmdMRCUpdateSingleClient.Enabled = false;
    tsStatusBar.Value = 0;
    tsStatusBar.Visible = true;
    DateTime YearAndMonth = new DateTime(dtpMRC.Value.Year, dtpMRC.Value.Month, 1);
    List<string> List = new List<string>();
    using (wotcDB DB = new wotcDB()) {
        var r = DB.client_main.
            Where(t => t.Active == true).
            OrderBy(t => t.CLIENTCODE).
            Select(t => t.CLIENTCODE);
        List.AddRange(await r.ToArrayAsync());
        tsStatusBar.Maximum = List.Count;
        foreach (var Client in List) {
            await Task.Run(() => {
                DB.system_MRC(Client, YearAndMonth);
            });
            tsStatusBar.Value++;
        }
    }
    cmdMRCUpdateAllExcelSheets.Enabled = true;
    cmdMRCUpdateSingleClient.Enabled = true;
    tsStatusBar.Visible = false;
    tsStatusBar.Value = 0;
    MessageBox.Show("Monthly Results and Changes - Task Complete");
}

这是否是正确的方法尚不清楚。使用阅读器时绝对不是。

【讨论】:

  • 这里还有很多问题,比如当你可以使用.ToListAsync()时使用List.AddRange。或者只是在循环中使用返回的数组而不是列表。您可以在任何标有async 的方法中使用ConfigureAwait(false),但您不需要这样做,除非有其他东西阻塞了UI 线程。坦率地说,将数据访问代码提取到标记为async Task 的单独方法中会更简洁,而不是将所有内容都放在事件处理程序中。
  • 至于system_MRC 也让它异步。将其映射到查询还是存储过程都没有关系。所有 EF 执行方法都有异步替代方案,例如ExecuteSqlCommandAsync
  • 我改用字符串数组。我必须将这些值拉入一个数组,因为我使用它们来设置我的进度条上的最大值,并且调用 .count 会比简单地存储小列表和使用 foreach 花费更多的处理成本。您评论中的链接用于使用原始 SQL。我专门使用 EF 来跟踪数据库与我的代码的更改。我试图通过调用 await 使 DB.system_MRC 异步,并且每种方式都给出了语法错误。我想将这一切保持在相同的方法中,因为它从未在其他任何地方再次使用过,所以我避免使用其他方法。
  • 然后在问题中发布您的代码! system_MRC 是做什么的?它是如何映射的,它是如何执行映射的存储过程的?不,您不需要使用List.AddRange,因为您不修改列表。一个数组就够了。
  • 我说我换成了一个字符串[]。我已经添加了程序大纲,程序很长,所以我删除了你不需要看的代码。它使用 ADO.NET 实体数据模型子选项 EF Designer 从数据映射...进行更新我只需删除模型中的三个条目并使用从数据库更新模型重新生成...
猜你喜欢
  • 2016-09-07
  • 1970-01-01
  • 2020-07-16
  • 2011-04-04
  • 2016-10-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多