可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have an async
method:
public async Task<string> GenerateCodeAsync()
{
string code = await GenerateCodeService.GenerateCodeAsync();
return code;
}
I need to call this method from a synchronous method.
How can I do this without having to duplicate the GenerateCodeAsync
method in order for this to work synchronously?
Update
Yet no reasonable solution found.
However, i see that HttpClient
already implements this pattern
using (HttpClient client = new HttpClient())
{
// async
HttpResponseMessage responseAsync = await client.GetAsync(url);
// sync
HttpResponseMessage responseSync = client.GetAsync(url).Result;
}
回答1:
You can access the Result
property of the task, which will cause your thread to block until the result is available:
string code = GenerateCodeAsync().Result;
Note: In some cases, this might lead to a deadlock: Your call to Result
blocks the main thread, thereby preventing the remainder of the async code to execute. You have the following options to make sure that this doesn\'t happen:
回答2:
You should get the awaiter (GetAwaiter()
) and end the wait for the completion of the asynchronous task (GetResult()
).
string code = GenerateCodeAsync().GetAwaiter().GetResult();
回答3:
You should be able to get this done using delegates, lambda expression
private void button2_Click(object sender, EventArgs e)
{
label1.Text = \"waiting....\";
Task<string> sCode = Task.Run(async () =>
{
string msg =await GenerateCodeAsync();
return msg;
});
label1.Text += sCode.Result;
}
private Task<string> GenerateCodeAsync()
{
return Task.Run<string>(() => GenerateCode());
}
private string GenerateCode()
{
Thread.Sleep(2000);
return \"I m back\" ;
}
回答4:
I need to call this method from a synchronously method.
It\'s possible with GenerateCodeAsync().Result
or GenerateCodeAsync().Wait()
, as the other answer suggests. This would block the current thread until GenerateCodeAsync
has completed.
However, your question is tagged with asp.net, and you also left the comment:
I was hoping for a simpler solution, thinking that asp.net handled
this much easier than writing so many lines of code
My point is, you should not be blocking on an asynchronous method in ASP.NET. This will reduce the scalability of your web app, and may create a deadlock (when an await
continuation inside GenerateCodeAsync
is posted to AspNetSynchronizationContext
). Using Task.Run(...).Result
to offload something to a pool thread and then block will hurt the scalability even more, as it incurs +1 more thread to process a given HTTP request.
ASP.NET has built-in support for asynchronous methods, either through asynchronous controllers (in ASP.NET MVC and Web API) or directly via AsyncManager
and PageAsyncTask
in classic ASP.NET. You should use it. For more details, check this answer.
回答5:
Microsoft Identity has extension methods which call async methods synchronously.
For example there is GenerateUserIdentityAsync() method and equal CreateIdentity()
If you look at UserManagerExtensions.CreateIdentity()
it look like this:
public static ClaimsIdentity CreateIdentity<TUser, TKey>(this UserManager<TUser, TKey> manager, TUser user,
string authenticationType)
where TKey : IEquatable<TKey>
where TUser : class, IUser<TKey>
{
if (manager == null)
{
throw new ArgumentNullException(\"manager\");
}
return AsyncHelper.RunSync(() => manager.CreateIdentityAsync(user, authenticationType));
}
Now lets see what AsyncHelper.RunSync does
public static TResult RunSync<TResult>(Func<Task<TResult>> func)
{
var cultureUi = CultureInfo.CurrentUICulture;
var culture = CultureInfo.CurrentCulture;
return _myTaskFactory.StartNew(() =>
{
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = cultureUi;
return func();
}).Unwrap().GetAwaiter().GetResult();
}
So, this is your wrapper for async method.
And please don\'t read data from Result - it will potentially block your code in ASP.
There is another way - which is suspicious for me, but you can consider it too
Result r = null;
YourAsyncMethod()
.ContinueWith(t =>
{
r = t.Result;
})
.Wait();
回答6:
To prevent deadlocks I always try to use Task.Run()
when I have to call an async method synchronously that @Heinzi mentions.
However the method has to be modified if the async method uses parameters. For example Task.Run(GenerateCodeAsync(\"test\")).Result
gives the error:
Argument 1: cannot convert from \'System.Threading.Tasks.Task<string>
\'
to \'System.Action\'
This could be called like this instead:
string code = Task.Run(() => GenerateCodeAsync(\"test\")).Result;