16

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

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

    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() 的一个警告是,异步函数中的所有等待语句都必须使用 .ConfigureAwait(false) 以避免死锁(如此处所述),但因为我正在编写一个永远不会需要访问 UI 或 WebContext 我可以安全地这样做。

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

想法/建议表示赞赏。

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

4

3 回答 3

12

我希望每个操作都有同步和异步功能,因为这个库将被控制台和 GUI 应用程序使用,但我不想到处重复代码。

最好的答案是:不要。

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

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

于 2012-07-26T17:15:03.900 回答
1

我有类似的情况,一些应用程序需要同步加载数据,而其他应用程序需要异步加载。我决定创建一个我称之为数据加载器的接口:

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

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

public delegate void AssignProjects(IEnumerable<Project> results);

现在,它的美妙之处在于您可以在不知道您是同步处理还是异步处理的情况下使用界面。

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

 public abstract class BaseViewModelDL {
    protected IEnumerable<Project> 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<Project>(base.LoadProjects());
        e.Result = new object[] { results, e.Argument };
    }

    void LoadProjectsAsync_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
        AssignProjects callback = (AssignProjects)((object[])e.Result)[1];
        IEnumerable<Project> results = (IEnumerable<Project>)((object[])e.Result)[0];
        callback(results);
    }

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

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

现在,您的调用代码看起来相同,并且不知道它是异步还是同步:

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

我经常将它用于单元测试(同步)、WPF 应用程序(异步)和控制台应用程序(同步)。

于 2012-07-26T17:04:18.360 回答
0

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

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

来自:异步

于 2012-07-26T16:55:19.170 回答