【问题标题】:How can I prevent one async method from monopolizing another?如何防止一种异步方法垄断另一种方法?
【发布时间】:2021-03-25 08:17:08
【问题描述】:

在我的 UWP 应用中,我有一个异步方法(事件处理程序),它调用另一个异步方法,该方法尝试将记录插入数据库。

我在插入尝试中遇到了异常,并试图解释为什么会发生这种情况。所以我在 InsertMapRecord() 方法的第一个“使用”行上放了一个断点:

using (SqliteConnection conn = new SqliteConnection(connStr))

当我到达那个断点时,我按下了 F10,但不是将我带到 Insert 方法中的下一行,而是将我带到 btnCre8NewMap_Click() 中的这一行,事件处理程序(已经被击中,你会想一想,为了达到上一行):

InsertMapRecord(mapName, mapNotes, defaultZoomLevel);

然后我按 F11,试图返回到 InsertMapRecord() 方法,但我最终在 App.g.i.cs 上,在这一行:

#if DEBUG && !DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION
            UnhandledException += (sender, e) =>
            {
                if (global::System.Diagnostics.Debugger.IsAttached) global::System.Diagnostics.Debugger.Break();
            };
#endif

...突出显示“global::System.Diagnostics.Debugger.Break()”,然后显示以下异常消息:

完整的方法如下

private async void btnCre8NewMap_Click(object sender, RoutedEventArgs e)
{
    try
    {
        string mapName = string.Empty;
        string mapNotes = string.Empty;
        int defaultZoomLevel = 1;
        ClearLocations();
        // Popul8 the cmbx
        for (int i = 1; i < 20; i++)
        {
            cmbxCre8MapZoomLevels.Items.Add(i.ToString());
        }
        ContentDialogResult result = await cntDlgCre8Map.ShowAsync();

        if (result == ContentDialogResult.Primary)
        {
            mapName = txtbxMapName.Text;
            mapNotes = txtbxMapNotes.Text;
            defaultZoomLevel = cmbxCre8MapZoomLevels.SelectedIndex + 1;
            InsertMapRecord(mapName, mapNotes, defaultZoomLevel);
        }
        // else do nothing (don't save)
    }
    catch (Exception ex)
    {
        MessageDialog exceptionMsgDlg = new MessageDialog(ex.Message, "btnCre8NewMap_Click");
        await exceptionMsgDlg.ShowAsync();
    }
}

private async void InsertMapRecord(string mapName, string mapNotes, int preferredZoomLevel)
{
    path = folder.Path;
    connStr = string.Format(connStrBase, path);
    try
    {
        using (SqliteConnection conn = new SqliteConnection(connStr))
        {
            String query = "INSERT INTO dbo.CartographerMain " +
                "(MapName, MapNotes, PreferredZoomLevel) " +
                "VALUES (@MapName, @MapNotes, @PreferredZoomLevel)";

            using (SqliteCommand cmd = new SqliteCommand(query, conn))
            {
                cmd.Parameters.AddWithValue("@MapName", mapName);
                cmd.Parameters.AddWithValue("@MapNotes", mapNotes);
                cmd.Parameters.AddWithValue("@PreferredZoomLevel", preferredZoomLevel);
                conn.Open();
                int result = cmd.ExecuteNonQuery();

                if (result < 0)
                {
                    MessageDialog dialog = new MessageDialog("Error inserting data into CartographerMain");
                    await dialog.ShowAsync();
                }
            }
        }
    }
    catch (SqliteException sqlex)
    {
        MessageDialog dialog = new MessageDialog(sqlex.Message, "InsertMapRecord");
        await dialog.ShowAsync();
    }
}

【问题讨论】:

  • 这段代码有很多问题,但主要是你有一个async void method
  • 我的建议是将async void InsertMapRecord 更改为async Task InsertMapRecord,然后将await 这个方法在你调用它的任何地方,看看它是否有什么不同。

标签: c# sqlite uwp async-await


【解决方案1】:

InsertMapRecord 方法应该返回一个可以被调用者等待的Task。此外,当您打开与数据库的连接或执行查询时,它不应阻塞:

private async Task InsertMapRecord(string mapName, string mapNotes, int preferredZoomLevel)
{
    path = folder.Path;
    connStr = string.Format(connStrBase, path);
    try
    {
        using (SqliteConnection conn = new SqliteConnection(connStr))
        {
            String query = "INSERT INTO dbo.CartographerMain " +
                "(MapName, MapNotes, PreferredZoomLevel) " +
                "VALUES (@MapName, @MapNotes, @PreferredZoomLevel)";

            using (SqliteCommand cmd = new SqliteCommand(query, conn))
            {
                cmd.Parameters.AddWithValue("@MapName", mapName);
                cmd.Parameters.AddWithValue("@MapNotes", mapNotes);
                cmd.Parameters.AddWithValue("@PreferredZoomLevel", preferredZoomLevel);
                await conn.OpenAsync();
                int result = await cmd.ExecuteNonQueryAsync();

                if (result < 0)
                {
                    MessageDialog dialog = new MessageDialog("Error inserting data into CartographerMain");
                    await dialog.ShowAsync();
                }
            }
        }
    }
    catch (SqliteException sqlex)
    {
        MessageDialog dialog = new MessageDialog(sqlex.Message, "InsertMapRecord");
        await dialog.ShowAsync();
    }
}

应避免使用异步void 方法(事件处理程序除外)。

在您的事件处理程序中,您应该等待 InsertMapRecord 方法和任何其他异步方法:

if (result == ContentDialogResult.Primary)
{
    mapName = txtbxMapName.Text;
    mapNotes = txtbxMapNotes.Text;
    defaultZoomLevel = cmbxCre8MapZoomLevels.SelectedIndex + 1;
    await InsertMapRecord(mapName, mapNotes, defaultZoomLevel);
}

如果您这样做,您应该能够捕获任何异常并进一步调查。

【讨论】:

    【解决方案2】:

    由于事件处理程序返回 void 并且 async/await 模式要求该方法返回某种类型的 Task,因此应用程序会任意移动到您的事件处理程序之外。

    这种情况很可能是,当应用程序在断点处等待您的交互时,线程屈服了。这允许应用程序继续运行,并且由于您的事件处理程序返回 void,它没有等待上下文继续。

    有关在事件处理程序中使用 async/await 的帮助,请参阅此答案:https://stackoverflow.com/a/27763068/7241762

    还可以查看微软关于 async/await 的速成课程:https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/

    为了更清楚,这些症状似乎表明您在调用 async 方法时缺少 await(请参阅对 InsertMapRecord 的调用),并且缺少 TaskTask&lt;T&gt; async 方法的返回类型导致应用程序出现同步问题。 C# 中的事件处理程序需要返回 void,但有一些解决方法,例如在另一个问题的链接答案中解释的解决方法。

    【讨论】:

      猜你喜欢
      • 2011-10-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-30
      • 2013-02-10
      相关资源
      最近更新 更多