将同步方法转换为异步方法

11

我正在尝试将一段旧代码中的同步方法转化为异步方法,但我并不太理解。从我看过的所有视频和教程中,它们似乎都创建了两个方法:一个是实际函数,另一个是包装器,然后在UI上调用包装器。

这是我的代码:

private async Task<bool> login(String username, String password)
{
        var tcs = new TaskCompletionSource<RestSharp.IRestResponse>();

        RestSharp.RestRequest request = new RestSharp.RestRequest("/accounts/login/", RestSharp.Method.GET);
        RestSharp.IRestResponse response = client.Execute(request);

        // Make the login request
        request = new RestSharp.RestRequest("/accounts/login/", RestSharp.Method.POST);
        request.AddParameter("username", username);
        request.AddParameter("password", password);

        response = client.Execute(request);

        // Return loggin status
        dom = response.Content;
        return dom["html"].HasClass("logged-in"); 
}

由于某种原因,当我尝试从按钮点击调用UI线程上的方法时,它要求我使按钮事件变成async

txtLog.AppendText("Before Await");

Task<bool> result = await login("","");

txtLog.AppendText("After Await");
txtLog.AppendText("Result: " + result.toString());

我是否需要一个包装方法,也要设置为async,然后调用登录呢?

2个回答

15

首先回答你的第二个问题,如果你想在代码中使用关键字await,必须将按钮的事件标记为async

其次,如果一个函数使用async而没有在其内部使用await,那么该代码将不会异步运行。你需要创建一个任务并在其中运行同步方法,或者将该方法重写为异步方法。

作为任务方法:

private async void button1_Click(object sender, EventArgs e)
{
    txtLog.AppendText("Before Await");

    //Note I changed from "Task<bool>" to "bool", await is like calling ".Result" 
    //  on a task but not blocking the UI, so you store the type you are waiting for.
    bool result = await Task.Run(() => login("","")); //You would still use your old login code before you tried to make it async, it requires no modifications.

    txtLog.AppendText("After Await");
    txtLog.AppendText("Result: " + result.ToString());
}

重写函数方法:

private async void button1_Click(object sender, EventArgs e)
{
    txtLog.AppendText("Before Await");

    //Note I changed from "Task<bool>" to "bool", await is like calling ".Result" 
    //  on a task but not blocking the UI, so you store the type you are waiting for.
    bool result = await login("",""); 

    txtLog.AppendText("After Await");
    txtLog.AppendText("Result: " + result.ToString());
}

private Task<bool> login(String username, String password)
{
    var tcs = new TaskCompletionSource<bool>();

    // Make the login request
    var request = new RestSharp.RestRequest("/accounts/login/", RestSharp.Method.POST);
    request.AddParameter("username", username);
    request.AddParameter("password", password);

    client.ExecuteAsync(request, (response, handle) =>
        {
            try
            {
                // Return loggin status
                var dom = response.Content;

                //dom["html"] did not have a .HasClass in my tests, so this code may need work.
                tcs.TrySetResult(dom["html"].HasClass("logged-in")); 
            }
            catch(Exception ex)
            {
                tcs.TrySetException(ex);
            }
        });

    return tcs.Task;
}

在我的“重写方法”中,我使用了IRestClient的一部分——ExecuteAsync。该函数在完成后调用回调方法,在回调方法中,我调用tcsSetResult来报告我想要的结果。
你可以进一步扩展它,通过接收一个CancellationToken,如果引发了令牌,就在RestRequestAsyncHandle上调用Abort(),但是如果这样做,我们需要将async带回到函数中并等待结果,以便我们可以在取消令牌注册之后进行清理。
private Task<bool> login(String username, String password)
{
    return login(username, password, CancellationToken.None);
}

private async Task<bool> login(String username, String password, CancellationToken cancelToken)
{
    var tcs = new TaskCompletionSource<bool>();

    // Make the login request
    var request = new RestSharp.RestRequest("/accounts/login/", RestSharp.Method.POST);
    request.AddParameter("username", username);
    request.AddParameter("password", password);

    var asyncHandle = client.ExecuteAsync(request, (response, handle) =>
        {
            try
            {
                // Return loggin status
                var dom = response.Content;

                tcs.TrySetResult(dom["html"].HasClass("logged-in")); 
            }
            catch(Exception ex)
            {
                tcs.TrySetException(ex);
            }
        });

    //if the token is canceled it will call `asyncHandle.Abort()` for us.
    using(cancelToken.Register(() =>
        {
           if(tcs.TrySetCanceled(cancelToken))
               asyncHandle.Abort();
        }))
    {
        return await tcs.Task;
    }
}

1
你的按钮处理程序使用了await关键字,这要求它被标记为asyncawait关键字基本上将方法在await处分割,将await后面的部分转换为一个委托,在等待的Task完成时继续执行。方法在遇到await后立即返回。
你的登录函数没有使用await,也不需要async关键字。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接