同步调用异步方法

我有一个async方法:

public async Task<string> GenerateCodeAsync()
{
string code = await GenerateCodeService.GenerateCodeAsync();
return code;
}

我需要从一个同步方法调用这个方法。

我如何才能做到这一点,而不必复制GenerateCodeAsync方法,以使其同步工作?

更新

但没有找到合理的解决方案。

然而,我看到HttpClient已经实现了这个模式

using (HttpClient client = new HttpClient())
{
// async
HttpResponseMessage responseAsync = await client.GetAsync(url);


// sync
HttpResponseMessage responseSync = client.GetAsync(url).Result;
}
313343 次浏览

你可以访问任务的Result属性,这将导致线程阻塞,直到结果可用:

string code = GenerateCodeAsync().Result;

注意:在某些情况下,这可能会导致死锁:对Result的调用阻塞主线程,从而阻止异步代码的其余部分执行。你有以下选项来确保这种情况不会发生:

这个意味着你应该在所有的异步调用之后无意识地添加.ConfigureAwait(false) !有关为什么以及何时应该使用.ConfigureAwait(false)的详细分析,请参阅以下博客文章:

我需要从同步方法调用这个方法。

这可能是GenerateCodeAsync().ResultGenerateCodeAsync().Wait(),正如另一个答案所示。这将阻塞当前线程,直到GenerateCodeAsync完成。

但是,你的问题被标记为,并且你也留下了评论:

我希望有一个更简单的解决方案,认为asp.net处理 这比写这么多行代码要容易得多

我的观点是,你在ASP.NET的异步方法上不应该阻塞。这将降低web应用程序的可伸缩性,并可能产生死锁(当GenerateCodeAsync中的await延续被发布到AspNetSynchronizationContext时)。使用Task.Run(...).Result将一些内容卸载到池线程,然后阻塞将进一步损害可伸缩性,因为它会导致+1个线程来处理给定的HTTP请求。

ASP。NET内置了对异步方法的支持,可以通过异步控制器(在ASP。NET MVC和Web API)或直接通过经典ASP.NET中的AsyncManagerPageAsyncTask。你应该使用它。要了解更多细节,请查看这个答案

你应该可以用委托,lambda表达式来完成

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" ;
}

你应该获取awaiter (GetAwaiter())并结束异步任务完成的等待(GetResult())。

string code = GenerateCodeAsync().GetAwaiter().GetResult();
Microsoft Identity具有同步调用异步方法的扩展方法。 例如,有GenerateUserIdentityAsync()方法和CreateIdentity()

如果你查看UserManagerExtensions.CreateIdentity() 它看起来像这样:

 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));
}

现在让我们看看AsyncHelper。RunSync确实

  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();
}
这是async方法的包装器。 请不要从Result中读取数据——它可能会在ASP中阻塞你的代码。< / p >

还有另一种方法——对我来说有点可疑,但你也可以考虑一下

  Result r = null;


YourAsyncMethod()
.ContinueWith(t =>
{
r = t.Result;
})
.Wait();

为了防止死锁,当我必须同步调用@Heinzi提到的异步方法时,我总是尝试使用Task.Run()

但是,如果异步方法使用参数,则必须修改该方法。例如,Task.Run(GenerateCodeAsync("test")).Result给出错误:

参数1:不能从'System.Threading.Tasks.Task<string>'转换 的系统。行动”< / p >

它可以被这样调用:

string code = Task.Run(() => GenerateCodeAsync("test")).Result;

如果你有一个名为“RefreshList”的异步方法,那么你可以从一个如下所示的非异步方法调用该异步方法。

Task.Run(async () => { await RefreshList(); });

这个线程上的大多数答案要么很复杂,要么会导致死锁。

下面的方法很简单,它将避免死锁,因为我们正在等待任务完成,然后才得到它的结果-

var task = Task.Run(() => GenerateCodeAsync());
task.Wait();
string code = task.Result;
此外,这里有一篇引用MSDN的文章,谈论的是完全相同的事情- https://blogs.msdn.microsoft.com/jpsanders/2017/08/28/asp-net-do-not-use-task-result-in-main-context/ < / p >

我更喜欢非阻塞的方法:

            Dim aw1=GenerateCodeAsync().GetAwaiter()
While Not aw1.IsCompleted
Application.DoEvents()
End While

我正在使用这种方法,它也将处理和传播来自底层异步任务的异常。

    private string RunSync()
{
var task = Task.Run(async () => await GenerateCodeService.GenerateCodeAsync());
if (task.IsFaulted && task.Exception != null)
{
throw task.Exception;
}


return task.Result;
}

编辑:

任务有等待方法Task.Wait(),它等待“承诺”解决,然后继续,从而呈现同步。 例子:< / p >


async Task<String> MyAsyncMethod() { ... }


String mySyncMethod() {


return MyAsyncMethod().Wait();
}

有些扩展方法异步等待异步操作的完成,然后设置ManualResetEvent来指示完成。

注意:你可以使用Task.Run(),但是扩展方法是一个更清晰的接口来表达你真正想要的东西。

显示如何使用扩展的测试:

    [TestClass]
public class TaskExtensionsTests
{
[TestMethod]
public void AsynchronousOperationWithNoResult()
{
SampleAsynchronousOperationWithNoResult().AwaitResult();
}


[TestMethod]
public void AsynchronousOperationWithResult()
{
Assert.AreEqual(3, SampleAsynchronousOperationWithResult(3).AwaitResult());
}


[TestMethod]
[ExpectedException(typeof(Exception))]
public void AsynchronousOperationWithNoResultThrows()
{
SampleAsynchronousOperationWithNoResultThrows().AwaitResult();
}


[TestMethod]
[ExpectedException(typeof(Exception))]
public void AsynchronousOperationWithResultThrows()
{
SampleAsynchronousOperationWithResultThrows(3).AwaitResult();
}


private static async Task SampleAsynchronousOperationWithNoResult()
{
await Task.Yield();
}


private static async Task<T> SampleAsynchronousOperationWithResult<T>(T result)
{
await Task.Yield();
return result;
}


private static async Task SampleAsynchronousOperationWithNoResultThrows()
{
await Task.Yield();
throw new Exception();
}


private static async Task<T> SampleAsynchronousOperationWithResultThrows<T>(T result)
{
await Task.Yield();
throw new Exception();
}


[TestMethod]
public void AsynchronousValueOperationWithNoResult()
{
SampleAsynchronousValueOperationWithNoResult().AwaitResult();
}


[TestMethod]
public void AsynchronousValueOperationWithResult()
{
Assert.AreEqual(3, SampleAsynchronousValueOperationWithResult(3).AwaitResult());
}


[TestMethod]
[ExpectedException(typeof(Exception))]
public void AsynchronousValueOperationWithNoResultThrows()
{
SampleAsynchronousValueOperationWithNoResultThrows().AwaitResult();
}


[TestMethod]
[ExpectedException(typeof(Exception))]
public void AsynchronousValueOperationWithResultThrows()
{
SampleAsynchronousValueOperationWithResultThrows(3).AwaitResult();
}


private static async ValueTask SampleAsynchronousValueOperationWithNoResult()
{
await Task.Yield();
}


private static async ValueTask<T> SampleAsynchronousValueOperationWithResult<T>(T result)
{
await Task.Yield();
return result;
}


private static async ValueTask SampleAsynchronousValueOperationWithNoResultThrows()
{
await Task.Yield();
throw new Exception();
}


private static async ValueTask<T> SampleAsynchronousValueOperationWithResultThrows<T>(T result)
{
await Task.Yield();
throw new Exception();
}
}

扩展

    /// <summary>
/// Defines extension methods for <see cref="Task"/> and <see cref="ValueTask"/>.
/// </summary>
public static class TaskExtensions
{
/// <summary>
/// Synchronously await the results of an asynchronous operation without deadlocking; ignoring cancellation.
/// </summary>
/// <param name="task">
/// The <see cref="Task"/> representing the pending operation.
/// </param>
public static void AwaitCompletion(this ValueTask task)
{
new SynchronousAwaiter(task, true).GetResult();
}


/// <summary>
/// Synchronously await the results of an asynchronous operation without deadlocking; ignoring cancellation.
/// </summary>
/// <param name="task">
/// The <see cref="Task"/> representing the pending operation.
/// </param>
public static void AwaitCompletion(this Task task)
{
new SynchronousAwaiter(task, true).GetResult();
}


/// <summary>
/// Synchronously await the results of an asynchronous operation without deadlocking.
/// </summary>
/// <param name="task">
/// The <see cref="Task"/> representing the pending operation.
/// </param>
/// <typeparam name="T">
/// The result type of the operation.
/// </typeparam>
/// <returns>
/// The result of the operation.
/// </returns>
public static T AwaitResult<T>(this Task<T> task)
{
return new SynchronousAwaiter<T>(task).GetResult();
}


/// <summary>
/// Synchronously await the results of an asynchronous operation without deadlocking.
/// </summary>
/// <param name="task">
/// The <see cref="Task"/> representing the pending operation.
/// </param>
public static void AwaitResult(this Task task)
{
new SynchronousAwaiter(task).GetResult();
}


/// <summary>
/// Synchronously await the results of an asynchronous operation without deadlocking.
/// </summary>
/// <param name="task">
/// The <see cref="ValueTask"/> representing the pending operation.
/// </param>
/// <typeparam name="T">
/// The result type of the operation.
/// </typeparam>
/// <returns>
/// The result of the operation.
/// </returns>
public static T AwaitResult<T>(this ValueTask<T> task)
{
return new SynchronousAwaiter<T>(task).GetResult();
}


/// <summary>
/// Synchronously await the results of an asynchronous operation without deadlocking.
/// </summary>
/// <param name="task">
/// The <see cref="ValueTask"/> representing the pending operation.
/// </param>
public static void AwaitResult(this ValueTask task)
{
new SynchronousAwaiter(task).GetResult();
}


/// <summary>
/// Ignore the <see cref="OperationCanceledException"/> if the operation is cancelled.
/// </summary>
/// <param name="task">
/// The <see cref="Task"/> representing the asynchronous operation whose cancellation is to be ignored.
/// </param>
/// <returns>
/// The <see cref="Task"/> representing the asynchronous operation whose cancellation is ignored.
/// </returns>
public static async Task IgnoreCancellationResult(this Task task)
{
try
{
await task.ConfigureAwait(false);
}
catch (OperationCanceledException)
{
}
}


/// <summary>
/// Ignore the <see cref="OperationCanceledException"/> if the operation is cancelled.
/// </summary>
/// <param name="task">
/// The <see cref="ValueTask"/> representing the asynchronous operation whose cancellation is to be ignored.
/// </param>
/// <returns>
/// The <see cref="ValueTask"/> representing the asynchronous operation whose cancellation is ignored.
/// </returns>
public static async ValueTask IgnoreCancellationResult(this ValueTask task)
{
try
{
await task.ConfigureAwait(false);
}
catch (OperationCanceledException)
{
}
}


/// <summary>
/// Ignore the results of an asynchronous operation allowing it to run and die silently in the background.
/// </summary>
/// <param name="task">
/// The <see cref="Task"/> representing the asynchronous operation whose results are to be ignored.
/// </param>
public static async void IgnoreResult(this Task task)
{
try
{
await task.ConfigureAwait(false);
}
catch
{
// ignore exceptions
}
}


/// <summary>
/// Ignore the results of an asynchronous operation allowing it to run and die silently in the background.
/// </summary>
/// <param name="task">
/// The <see cref="ValueTask"/> representing the asynchronous operation whose results are to be ignored.
/// </param>
public static async void IgnoreResult(this ValueTask task)
{
try
{
await task.ConfigureAwait(false);
}
catch
{
// ignore exceptions
}
}
}


/// <summary>
/// Internal class for waiting for asynchronous operations that have a result.
/// </summary>
/// <typeparam name="TResult">
/// The result type.
/// </typeparam>
public class SynchronousAwaiter<TResult>
{
/// <summary>
/// The manual reset event signaling completion.
/// </summary>
private readonly ManualResetEvent manualResetEvent;


/// <summary>
/// The exception thrown by the asynchronous operation.
/// </summary>
private Exception exception;


/// <summary>
/// The result of the asynchronous operation.
/// </summary>
private TResult result;


/// <summary>
/// Initializes a new instance of the <see cref="SynchronousAwaiter{TResult}"/> class.
/// </summary>
/// <param name="task">
/// The task representing an asynchronous operation.
/// </param>
public SynchronousAwaiter(Task<TResult> task)
{
this.manualResetEvent = new ManualResetEvent(false);
this.WaitFor(task);
}


/// <summary>
/// Initializes a new instance of the <see cref="SynchronousAwaiter{TResult}"/> class.
/// </summary>
/// <param name="task">
/// The task representing an asynchronous operation.
/// </param>
public SynchronousAwaiter(ValueTask<TResult> task)
{
this.manualResetEvent = new ManualResetEvent(false);
this.WaitFor(task);
}


/// <summary>
/// Gets a value indicating whether the operation is complete.
/// </summary>
public bool IsComplete => this.manualResetEvent.WaitOne(0);


/// <summary>
/// Synchronously get the result of an asynchronous operation.
/// </summary>
/// <returns>
/// The result of the asynchronous operation.
/// </returns>
public TResult GetResult()
{
this.manualResetEvent.WaitOne();
return this.exception != null ? throw this.exception : this.result;
}


/// <summary>
/// Tries to synchronously get the result of an asynchronous operation.
/// </summary>
/// <param name="operationResult">
/// The result of the operation.
/// </param>
/// <returns>
/// The result of the asynchronous operation.
/// </returns>
public bool TryGetResult(out TResult operationResult)
{
if (this.IsComplete)
{
operationResult = this.exception != null ? throw this.exception : this.result;
return true;
}


operationResult = default;
return false;
}


/// <summary>
/// Background "thread" which waits for the specified asynchronous operation to complete.
/// </summary>
/// <param name="task">
/// The task.
/// </param>
private async void WaitFor(Task<TResult> task)
{
try
{
this.result = await task.ConfigureAwait(false);
}
catch (Exception exception)
{
this.exception = exception;
}
finally
{
this.manualResetEvent.Set();
}
}


/// <summary>
/// Background "thread" which waits for the specified asynchronous operation to complete.
/// </summary>
/// <param name="task">
/// The task.
/// </param>
private async void WaitFor(ValueTask<TResult> task)
{
try
{
this.result = await task.ConfigureAwait(false);
}
catch (Exception exception)
{
this.exception = exception;
}
finally
{
this.manualResetEvent.Set();
}
}
}


/// <summary>
/// Internal class for  waiting for  asynchronous operations that have no result.
/// </summary>
public class SynchronousAwaiter
{
/// <summary>
/// The manual reset event signaling completion.
/// </summary>
private readonly ManualResetEvent manualResetEvent = new ManualResetEvent(false);


/// <summary>
/// The exception thrown by the asynchronous operation.
/// </summary>
private Exception exception;


/// <summary>
/// Initializes a new instance of the <see cref="SynchronousAwaiter{TResult}"/> class.
/// </summary>
/// <param name="task">
/// The task representing an asynchronous operation.
/// </param>
/// <param name="ignoreCancellation">
/// Indicates whether to ignore cancellation. Default is false.
/// </param>
public SynchronousAwaiter(Task task, bool ignoreCancellation = false)
{
this.manualResetEvent = new ManualResetEvent(false);
this.WaitFor(task, ignoreCancellation);
}


/// <summary>
/// Initializes a new instance of the <see cref="SynchronousAwaiter{TResult}"/> class.
/// </summary>
/// <param name="task">
/// The task representing an asynchronous operation.
/// </param>
/// <param name="ignoreCancellation">
/// Indicates whether to ignore cancellation. Default is false.
/// </param>
public SynchronousAwaiter(ValueTask task, bool ignoreCancellation = false)
{
this.manualResetEvent = new ManualResetEvent(false);
this.WaitFor(task, ignoreCancellation);
}


/// <summary>
/// Gets a value indicating whether the operation is complete.
/// </summary>
public bool IsComplete => this.manualResetEvent.WaitOne(0);


/// <summary>
/// Synchronously get the result of an asynchronous operation.
/// </summary>
public void GetResult()
{
this.manualResetEvent.WaitOne();
if (this.exception != null)
{
throw this.exception;
}
}


/// <summary>
/// Background "thread" which waits for the specified asynchronous operation to complete.
/// </summary>
/// <param name="task">
/// The task.
/// </param>
/// <param name="ignoreCancellation">
/// Indicates whether to ignore cancellation. Default is false.
/// </param>
private async void WaitFor(Task task, bool ignoreCancellation)
{
try
{
await task.ConfigureAwait(false);
}
catch (OperationCanceledException)
{
}
catch (Exception exception)
{
this.exception = exception;
}
finally
{
this.manualResetEvent.Set();
}
}


/// <summary>
/// Background "thread" which waits for the specified asynchronous operation to complete.
/// </summary>
/// <param name="task">
///     The task.
/// </param>
/// <param name="ignoreCancellation">
/// Indicates whether to ignore cancellation. Default is false.
/// </param>
private async void WaitFor(ValueTask task, bool ignoreCancellation)
{
try
{
await task.ConfigureAwait(false);
}
catch (OperationCanceledException)
{
}
catch (Exception exception)
{
this.exception = exception;
}
finally
{
this.manualResetEvent.Set();
}
}
}
}

您可以使用同步方法生成器库(nuget)来生成此代码的同步版本。

使用方法如下:

[Zomp.SyncMethodGenerator.CreateSyncVersion]
public async Task<string> GenerateCodeAsync()
{
string code = await GenerateCodeService.GenerateCodeAsync();
return code;
}

它将生成可以同步调用的GenerateCode方法。

将要生成的源是:

public string GenerateCode()
{
string code = GenerateCodeService.GenerateCode();
return code;
}