【问题标题】:C# Waiting for lambda callbacks to completeC# 等待 lambda 回调完成
【发布时间】:2019-12-23 18:42:49
【问题描述】:

总结

我目前正在尝试使用 MySQL 数据库库来执行 select 语句并以回调的方式将其返回到匿名语句 lambda。这就是这个特定的(大部分未记录的)库处理请求的方式。我也需要等待这个过程完成。

我目前正在尝试使用async 方法,但是似乎过早断言任务完成(即在回调完成之前绕过await taskName;,因此返回的字典为空)。

我曾尝试使用完成标志方法,其中使用布尔标志来表示回调是否已完成,并在返回任务之前在 while 循环中使用 Task.Yield()

下面是来自两个不同类的两个函数。第一个来自数据库类,第二个来自实用程序类(从中调用数据库类)。

代码

/// <summary>
/// Asynchronously executes a select MySQL statement and returns a dictionary of rows selected.
/// </summary>
/// <param name="statement"></param>
/// <returns></returns>
public async Task<Dictionary<int, object>> ExecuteSelectAsync (string statement)
{
    // Init dictionary of rows and counter for each row
    Dictionary<int, object> responseData = new Dictionary<int, object>();
    int i = 0;

    bool complete = false;

    DatabaseLibrary.execute(
        statement,
        new Action<dynamic> (s =>
        {
            // Take the data returned as 's' and populate the 'responseData' dictionary.
            Utility.LogDebug("Database", "Executed select statement with " + numberOfRows.ToString() + " rows");
        })
    );

    Utility.LogDebug("Database", "Returning select execution response"); // By this point, the lambda expression hasn't been executed.
    return responseData; // This is empty at time of return.
}
/// <summary>
/// Checks the supplied data against the database to validate.
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public static async Task<bool> ValidateData(string data)
{
    Database database = new Database();

    Task<Dictionary<int, object>> selectTask = database.ExecuteSelectAsync("SELECT fieldname FROM tablename WHERE data='" + data + "'"); // Excuse statement forming, this is just to test
    await selectTask;

    try
    {
        Dictionary<string, object> firstRow = (Dictionary<string, object>)selectTask.Result[0];

        if ((int)firstRow["fieldname"] == 0) return false; // data not valid, return false
        else return true; // data valid, return true
    }
    catch (Exception e)
    {
        LogException("Utility", e);
        LogDebug("Utility", "Database class returned empty result set");
        return false; // Empty result, presume false
    }            
}

我知道这段代码有效,在显示Returning select execution response 控制台输出后不久,输出第二行读取Executed select statement with x rows这里的主要问题是存在竞争条件。如何确保在处理数据之前正确填充数据?

【问题讨论】:

  • 我不认为共享可以识别竞争条件的代码。 execute() 方法里面有什么?
  • execute() 方法隐藏在 JavaScript 库中。这是将语句传递给 MySQL 并处理响应的地方。
  • DatabaseLibrary.execute 是否返回 Task?如果是这样,您应该使用await DatabaseLibrary.execute(...) 来“暂停”您的方法,直到数据库执行完成。
  • @BradleyGrainger 我已经尝试过了,但是 DatabaseLibrary.execute 是在运行时确定的(通过导出调用),awaiting 它会导致 RuntimeBinderException(无法执行运行时绑定在空引用上)。

标签: c# mysql asynchronous lambda callback


【解决方案1】:

您需要一种方法让数据库回调向您的代码发出信号,表明它已被调用,并且可以恢复执行。最简单的方法是使用TaskCompletionSource。它看起来像:

public async Task<Dictionary<int, object>> ExecuteSelectAsync (string statement)
{
    // declare the TaskCompletionSource that will hold the database results
    var tcs = new TaskCompletionSource<Dictionary<int, object>>();

    DatabaseLibrary.execute(
        statement,
        new Action<dynamic> (s =>
        {
            // Take the data returned as 's' and populate the 'responseData' dictionary.
            Utility.LogDebug("Database", "Executed select statement with " + numberOfRows.ToString() + " rows");

            var data = new Dictionary<int, object>();
            // build your dictionary here

            // the work is now complete; set the data on the TaskCompletionSource
            tcs.SetResult(data);
        })
    );

    // wait for the response data to be created
    var responseData = await tcs.Task;

    Utility.LogDebug("Database", "Returning select execution response"); 
    return responseData;

    // if you don't need the logging, you could delete the three lines above and
    // just 'return tcs.Task;' (or move the logging into the callback)
}

【讨论】:

  • 打败我。您还可以使用TaskCompletionSource&lt;dynamic&gt;(假设数据库查询的返回类型未知)并使用tcs.SetResult(s)。但这实际上只是一个偏好问题。
【解决方案2】:

扩展 Bradley 的回答:多年来,人们创造了多种方法来实现异步。较旧的方法是使用回调:您传入一个将在工作完成时调用的方法。这就是您的 execute 方法正在做的事情。

但我们发现这会产生令人困惑的代码,或者某些人称之为"callback hell"

所以async/await 被创建了。在 .NET 中,它被称为 Task-based asynchronous pattern (TAP),它允许您编写看起来像同步代码的异步代码,从而更容易理解正在发生的事情。

通过使用TaskCompletionSource,您将回调模式转换为基于任务的模式,以便您可以使用await 并使您的代码更易于理解。

【讨论】:

    猜你喜欢
    • 2016-03-09
    • 2016-02-17
    • 1970-01-01
    • 1970-01-01
    • 2014-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-22
    相关资源
    最近更新 更多