问题 我正在尝试将同步方法从一些旧代码转换为异步方法,但我有一些麻烦


我正在尝试将同步方法从一些旧代码转换为异步方法,但我有一些麻烦。从我读过的所有视频和教程中,他们似乎都在创建方法,一个是实际的函数,另一个是包装器,然后是在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线程上的方法时,它要求我使按钮事件异步。

txtLog.AppendText("Before Await");

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

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

我是否需要一个也设置为async的包装器方法来调用登录?

所有这些看起来有点复杂。


12062
2017-08-10 21:23


起源



答案:


要先回答你的第二部分,是的,你需要为按钮标记事件 async,如果你想使用关键字 await 在您的代码中,您必须声明该函数 async

如果功能使用,则为2 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;
}

在我的“重写方法”中,我正在做的是我正在使用 ExecuteAsync 女巫是 IRestClient的一部分。在我调用的回调方法中,该函数在完成时调用回调方法 tcsSetResult 报告我想要的结果。

您可以通过参加进一步扩展这一点 CancellationToken 如果令牌被提出呼叫 Abort() 上 RestRequestAsyncHandle但是,如果我们这样做,我们需要带来 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;
    }
}

13
2017-08-10 21:49



斯科特你刚刚回答了我的两个问题,因为我只是在查看如何使用ExecuteAsync,并对如何使用它感到困惑。另外,我使用的是具有“HasClass”方法的CsQuery。 - James Jeffery
我对可取消的版本进行了一些调整,你需要通知 tcs 工作被取消了。我还添加了对它报告可以在回调上抛出的任何异常的支持。 - Scott Chamberlain
这个答案对我有帮助。谢谢。 - Thomas.Benz
@ Thomas.Benz我刚刚意识到我犯了一个错误 cancelToken.Register,请参阅更新版本。 - Scott Chamberlain


答案:


要先回答你的第二部分,是的,你需要为按钮标记事件 async,如果你想使用关键字 await 在您的代码中,您必须声明该函数 async

如果功能使用,则为2 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;
}

在我的“重写方法”中,我正在做的是我正在使用 ExecuteAsync 女巫是 IRestClient的一部分。在我调用的回调方法中,该函数在完成时调用回调方法 tcsSetResult 报告我想要的结果。

您可以通过参加进一步扩展这一点 CancellationToken 如果令牌被提出呼叫 Abort() 上 RestRequestAsyncHandle但是,如果我们这样做,我们需要带来 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;
    }
}

13
2017-08-10 21:49



斯科特你刚刚回答了我的两个问题,因为我只是在查看如何使用ExecuteAsync,并对如何使用它感到困惑。另外,我使用的是具有“HasClass”方法的CsQuery。 - James Jeffery
我对可取消的版本进行了一些调整,你需要通知 tcs 工作被取消了。我还添加了对它报告可以在回调上抛出的任何异常的支持。 - Scott Chamberlain
这个答案对我有帮助。谢谢。 - Thomas.Benz
@ Thomas.Benz我刚刚意识到我犯了一个错误 cancelToken.Register,请参阅更新版本。 - Scott Chamberlain


您的按钮处理程序使用 await 关键字,需要制作它 async。该 await 关键字基本上分区方法 await,转过身后的部分 await 成为一个在等待时继续的代表 Task 完成。该方法遇到后立即返回 await

您的登录功能不使用 await。它不需要 async 关键词。


1
2017-08-10 21:28