C#有异步函数调用同步函数或同步函数调用异步函数
Posted
技术标签:
【中文标题】C#有异步函数调用同步函数或同步函数调用异步函数【英文标题】:C# have async function call synchronous function or synchronous function call async function 【发布时间】:2012-07-25 07:43:00 【问题描述】:我正在编写一个 C# .Net 4.5 库,用于执行常见的 sql 数据库操作(备份、恢复、执行脚本等)。我希望每个操作都有同步和异步功能,因为这个库将被控制台和 GUI 应用程序使用,但我不想到处重复代码。所以在我看来,我有两个选择:
编写在同步函数中完成工作的代码,然后将其包装在异步函数的任务中,如下所示:
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);
编写在异步函数中完成工作的代码,并使用 .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) 以避免死锁 (as discussed here),但是因为我正在编写一个库永远不需要访问 UI 或 WebContext 我可以安全地这样做。
我还要注意,SQL 库通常也有可以使用的同步和异步函数,所以如果在同步函数中工作,我会调用它们的同步函数,如果在异步中工作函数,我会调用他们的异步函数。
感谢您的想法/建议。
-- 编辑:我还发布了这个问题on the MSDN forums here 以尝试获得官方的 MS 回复--
【问题讨论】:
推荐的命名方式是异步方法以“Async”结尾。除此之外,这两种选择都不是更好。虽然,使用 ConfigureAwait(false) 确实限制了您的 api 的使用方式...... 我通常会来一些已经写好的东西。如果那里根本不支持异步,那么我会做你的例子 1。 【参考方案1】:我希望每个操作都有同步和异步函数,因为这个库将被控制台和 GUI 应用程序使用,但我不想到处重复代码。
最好的答案是:不要。
Stephen Toub 有两篇关于这个主题的优秀博文:
Should I expose asynchronous wrappers for synchronous methods? Should I expose synchronous wrappers for asynchronous methods?他建议将异步方法公开为异步,将同步方法公开为同步。如果您需要同时公开两者,则将通用功能封装在私有(同步)方法中,并复制异步/同步差异。
【讨论】:
感谢斯蒂芬的链接。我非常喜欢他的想法,即让开发人员使用库来包装同步到异步或异步到同步函数。这样他们就不会对函数的可伸缩性做出任何错误的假设,如果他们需要为响应式 UI 卸载它(例如使用 Task.Factory.StartNew(() => SomeSynchronousFunction()))或阻塞同步打电话他们仍然可以做到这一点。所以你说的答案是使异步函数异步,同步函数同步。根本不要包裹它们;把它留给图书馆/API的消费者。谢谢! 我也喜欢这个答案,不仅因为它可以帮助图书馆的消费者避免(如上所述),而且还有助于防止我的图书馆膨胀,因为我要创建同步和异步我的库中每个公共函数的版本,这也意味着重复的文档和所有其他令人头疼的问题,每个操作都有 2 个函数。【参考方案2】:我遇到过类似的情况,一些应用程序需要同步加载数据,而其他应用程序需要异步加载。我决定创建一个我称之为数据加载器的接口:
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 应用程序(异步)和控制台应用程序(同步)。
【讨论】:
【参考方案3】:似乎没有必要在不使用等待的情况下简单地将方法标记为异步。将其标记为 async 并不会使其异步,它允许您在方法体中使用 await(在 await 中执行的代码是异步发生的,然后 async 方法的其余部分也将异步完成):
通常,由 async 关键字修改的方法至少包含一个 await 表达式或语句。该方法同步运行,直到它到达第一个等待表达式,此时它被挂起,直到等待的任务完成。同时,控制权返回给方法的调用者。如果该方法不包含等待表达式或语句,则它会同步执行。编译器警告会提醒您注意任何不包含 await 的异步方法,因为这种情况可能表示错误。有关详细信息,请参阅编译器警告 CS4014。
发件人:async
【讨论】:
我认为 OP 会在该方法中执行某种“等待”。否则,将发出警告。 正确的彼得,我将等待实际执行操作(备份、恢复、运行脚本等)的 SQL 服务器函数调用,因为该工作将由数据库服务器完成,而不是我的库进程(即我的 PC 只是等待另一台 PC 上的操作完成;它不会自己处理)。以上是关于C#有异步函数调用同步函数或同步函数调用异步函数的主要内容,如果未能解决你的问题,请参考以下文章