异步任务永远不会在简单的 API 客户端中结束。僵局?

Async Task never ends in simple API client. Deadlock?

我是 C# 的新手,我很可能误解了 await、async 和 Tasks 的正确用法:)

我想开发一个 class (OWConnector) 作为我的应用程序的 API 客户端,为此我开发了一个通用的 PostRequest 方法来执行POST请求。

不幸的是,当我使用 auth 方法(使用通用方法 PostRequest)时,应用程序看起来陷入了僵局。

你能帮我看看问题出在哪里吗?我在代码中标记了调试器永远等待的地方。

// the debugger get stacked here :(

表格方法

 private void btnAnalyze_Click(object sender, EventArgs e)
 {
    OWConnector api = new OWConnector(@"http://mywebsite.com/");
    Boolean didAuth = api.auth("myuser", "mypass");
    if (didAuth)
    {
        MessageBox.Show(@"success :)");
    }
    else
    {
         MessageBox.Show(@"failed :(");
    }
}

OWConnector class

class OWConnector
{
    private CookieContainer cookieJar;
    private HttpClientHandler handler;
    private HttpClient client;
    private Uri baseUri; 

    public OWConnector(string baseUrl)
    {
        baseUri = new Uri(baseUrl);
        cookieJar = new CookieContainer();
        handler = new HttpClientHandler();
        handler.CookieContainer = cookieJar;
        handler.UseCookies = true;
        handler.AllowAutoRedirect = false;

        client = new HttpClient(handler);
        client.BaseAddress = baseUri;
    }

    public async Task<RequestResponse> PostRequest(string url, HttpContent data = null)
    {
        RequestResponse response = new RequestResponse();

        try
        {
            // the debugger get stacked here :(
            response.httpResponse = await client.PostAsync(url, data);  
        }
        catch (System.AggregateException e)
        {
            response.error = true;
            foreach (Exception ie in e.InnerExceptions)
            {
                response.errorMessage += ie.GetType().ToString() + ": " + ie.Message + "\n";
            }
        }

        return response;
    }

    public Boolean auth(string username, string password)
    {
        var content = new FormUrlEncodedContent(new[]{
            new KeyValuePair<string, string>(@"form_name", @"sign-in"),
            new KeyValuePair<string, string>(@"identity", username),
            new KeyValuePair<string, string>(@"password", password),
            new KeyValuePair<string, string>(@"remember", @"on"),
            new KeyValuePair<string, string>(@"submit", @"Sign In"),
        });

        RequestResponse r = PostRequest(@"/", content).Result;
        Boolean cookieFound = false;
        foreach (Cookie c in cookieJar.GetCookies(baseUri))
        {
            if (c.Name == @"ow_login")
            {
                cookieFound = true;
            }
        }

        return cookieFound;
    }
}

class RequestResponse
{
    public Boolean error;
    public string errorMessage;
    public HttpResponseMessage httpResponse;

    public RequestResponse()
    {
        error = false;
        errorMessage = @"";
    }
}

您正在使用 Task.Result 阻止异步操作。你在做 sync over async.

您的流程应该是 async 从事件处理程序(应该是 async void)一直向下(使用 async Task)。

private async void btnAnalyze_Click(object sender, EventArgs e)
{
    OWConnector api = new OWConnector(@"http://mywebsite.com/");
    Boolean didAuth = await api.authAsync("myuser", "mypass");
    if (didAuth)
    {
        MessageBox.Show(@"success :)");
    }
    else
    {
         MessageBox.Show(@"failed :(");
    }
}

class OWConnector
{
    // same as in OP...

    public async Task<bool> authAsync(string username, string password)
    {
        var content = new FormUrlEncodedContent(new[]{
            new KeyValuePair<string, string>(@"form_name", @"sign-in"),
            new KeyValuePair<string, string>(@"identity", username),
            new KeyValuePair<string, string>(@"password", password),
            new KeyValuePair<string, string>(@"remember", @"on"),
            new KeyValuePair<string, string>(@"submit", @"Sign In"),
        });

        RequestResponse r = await PostRequest(@"/", content);
        Boolean cookieFound = false;
        foreach (Cookie c in cookieJar.GetCookies(baseUri))
        {
            if (c.Name == @"ow_login")
            {
                cookieFound = true;
            }
        }

        return cookieFound;
    }
}

死锁的原因可能是存在 SynchronizationContext 并且您正在阻止需要 post 到 SC 才能完成的任务(因此,死锁)。虽然 async 一路解决了问题,但您还应该注意 ConfigureAwait(false) 忽略了捕获的 SC。所以更好的 authAsync 应该是:

public async Task<bool> authAsync(string username, string password)
{
    var content = new FormUrlEncodedContent(new[]{
        new KeyValuePair<string, string>(@"form_name", @"sign-in"),
        new KeyValuePair<string, string>(@"identity", username),
        new KeyValuePair<string, string>(@"password", password),
        new KeyValuePair<string, string>(@"remember", @"on"),
        new KeyValuePair<string, string>(@"submit", @"Sign In"),
    });

    RequestResponse r = await PostRequest(@"/", content).ConfigureAwait(false);
    Boolean cookieFound = false;
    foreach (Cookie c in cookieJar.GetCookies(baseUri))
    {
        if (c.Name == @"ow_login")
        {
            cookieFound = true;
        }
    }

    return cookieFound;
}