问题
I'm new to C# and I'm very likely misunderstanding the the proper usage of await,async and Tasks :)
I want to develop a class (OWConnector
) that serves as API client for my app, for this purpose I develop a generic PostRequest
method to perform POST request.
Unfortunately the app looks like is going into a deadlock when I user the auth method (that uses the generic method PostRequest
).
Can you help me to understand where is the problem ? I marked in the code where the debugger await forever.
// the debugger get stacked here :(
Form method
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 = @"";
}
}
回答1:
You're blocking on an asynchronous operation by using Task.Result
. You're doing sync over async.
Your flow should be async
all the way through from the event handler (that should be async void
) all the way down (with 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;
}
}
The reason for the deadlock is probably the fact that there's a SynchronizationContext
and you are blocking on a task that need to post to the SC to complete (hence, deadlock). While async
all the way solves the issue you should also be aware of ConfigureAwait(false)
that disregards the captured SC. So an even better authAsync
would be:
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;
}
来源:https://stackoverflow.com/questions/28110681/async-task-never-ends-in-simple-api-client-deadlock