C#具有异步函数调用同步函数或同步函数调用异步函数

我正在编写一个C#.Net 4.5库,用于执行常见的sql数据库操作(备份,恢复,执行脚本等)。 我希望每个操作都有同步和异步函数,因为这个库将由控制台和GUI应用程序使用,但我不想在任何地方重复代码。 所以我看到它,我有两个选择:

  1. 编写在同步函数中执行工作的代码,然后将其包装在async函数的任务中,如下所示:

    public void BackupDB(string server, string db) { // Do all of the work and long running operation here } public async Task BackupDBAsync(string server, string db) { await Task.Factory.StartNew(() => BackupDB(server, db)).ConfigureAwait(false); } 
  2. 编写在异步函数中执行工作的代码,并使用.Wait()从同步函数中调用它:

     public async Task BackupDBAsync(string server, string db) { // Do all of the work and long running operation here, asynchronously. } public void BackupDB(string server, string db) { BackupDBAsync(server, db).Wait(); // Execution will wait here until async function finishes completely. } 

一种选择比另一种更好吗? 这是最好的做法吗? 或者还有其他(更好的)替代方案吗?

我知道使用.Wait()的一个警告是async函数中的所有await语句都必须使用.ConfigureAwait(false)来避免死锁( 如此处所讨论的 ),但是因为我正在编写一个永远不会存在的库需要访问UI或WebContext我可以安全地做到这一点。

我还要注意,SQL库通常也有可以使用的同步和异步函数,所以如果在同步函数中工作,我会调用它们的同步函数,如果在异步函数中工作,我会调用他们的异步function。

赞赏的想法/建议。

– 编辑:我也在MSDN论坛上发布了这个问题,试图获得官方MS回复 –

我希望每个操作都有同步和异步函数,因为这个库将由控制台和GUI应用程序使用,但我不想在任何地方重复代码。

最好的答案是:不要。

Stephen Toub有两个关于这个主题的优秀博客文章:

  • 我应该为同步方法公开异步包装器吗?
  • 我应该为异步方法公开同步包装器吗?

他建议将异步方法公开为异步方法,将同步方法公开为同步方法。 如果需要公开两者,则将常用function封装在私有(同步)方法中,并复制异步/同步差异。

我有类似的情况,一些应用程序需要同步加载数据和其他asyc。 我决定创建一个我称为dataloader的界面:

 public interface IIMViewModelDL { void LoadProjects(AssignProjects callback); } 

AssignProjects回调只是一个简单的委托,它接收返回的项目列表:

 public delegate void AssignProjects(IEnumerable results); 

现在,这样做的好处是你可以使用界面,而无需知道你是在同步还是异步。

创建了三个类:一个基类,一个同步和一个异步:

  public abstract class BaseViewModelDL { protected IEnumerable LoadProjects() { BaseServiceClient client = new BaseServiceClient(); return client.Projects(); } public class SynchronousViewModelDL : BaseViewModelDL, IIMViewModelDL { public void LoadProjects(AssignProjects callback) { callback(base.LoadProjects()); } public class AsyncIMViewModelDL : BaseViewModelDL, IIMViewModelDL { public void LoadProjects(AssignProjects callback) { BackgroundWorker loadProjectsAsync = new BackgroundWorker(); loadProjectsAsync.DoWork += new DoWorkEventHandler(LoadProjectsAsync_DoWork); loadProjectsAsync.RunWorkerCompleted += new RunWorkerCompletedEventHandler(LoadProjectsAsync_RunWorkerCompleted); loadProjectsAsync.RunWorkerAsync(callback); } void LoadProjectsAsync_DoWork(object sender, DoWorkEventArgs e) { var results = new ObservableCollection(base.LoadProjects()); e.Result = new object[] { results, e.Argument }; } void LoadProjectsAsync_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { AssignProjects callback = (AssignProjects)((object[])e.Result)[1]; IEnumerable results = (IEnumerable)((object[])e.Result)[0]; callback(results); } 

现在,在您的应用程序中,您可以决定如何加载数据……这可以通过IoC容器注入,但是为了演示目的而硬编码:

 private ViewModelDataLoaders.IIMViewModelDL dataLoader = new ViewModelDataLoaders.AsyncIMViewModelDL(); 

现在,您的调用代码看起来相同,并且无论是异步还是同步都不是更明智的:

 private void LoadProjects() { dataLoader.LoadProjects( delegate(IEnumerable results) { Projects = new ObservableCollection(results); }); } 

我经常使用它进行unit testing(同步),WPF应用程序(异步)和控制台应用程序(同步)。

似乎没有必要简单地将方法标记为async而不使用await。 将其标记为异步并不会使其异步,它允许您在方法体中使用等待(在await中执行的代码是异步发生的代码,然后异步方法的其余部分也将异步完成):

通常,由async关键字修改的方法至少包含一个await表达式或语句。 该方法同步运行,直到它到达第一个await表达式,此时它将暂停,直到等待的任务完成。 同时,控制权返回给方法的调用者。 如果该方法不包含await表达式或语句,则它将同步执行。 编译器警告会提醒您任何不包含等待的异步方法,因为这种情况可能表示错误。 有关更多信息,请参阅编译器警告CS4014。

来自: 异步