我正在编写一个C#.Net 4.5库,用于执行常见的SQL数据库操作(备份,恢复,执行脚本等)。我希望每个操作都有同步和异步功能,因为这个库将由控制台和GUI应用程序使用,但我不想在任何地方复制代码。所以我看到它,我有两个选择:
在同步函数中编写完成工作的代码,然后将其包装在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);
}
在异步函数中编写完成工作的代码,并使用.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)来避免死锁(as discussed here),但是因为我'写一个永远不需要访问UI或WebContext的库我可以安全地做到这一点。
我还要注意,SQL库通常也同时具有可以使用的同步和异步功能,因此如果在同步功能中执行工作,我会调用它们的同步功能,如果在异步函数,我会称之为异步函数。
感谢您的建议/建议。
- 编辑:我还发布了此问题on the MSDN forums here以尝试获得正式的MS回复 -
答案 0 :(得分:12)
我希望每个操作都有同步和异步函数,因为这个库将由控制台和GUI应用程序使用,但我不想在任何地方复制代码。
最好的答案是:不要。
Stephen Toub有两篇关于这个主题的优秀博客文章:
他建议将异步方法公开为异步方法,将同步方法公开为同步方法。如果需要公开两者,则将常用功能封装在私有(同步)方法中,并复制异步/同步差异。
答案 1 :(得分:1)
我有类似的情况,其中一些应用程序需要同步加载数据而其他应用程序需要asyc。我决定创建一个名为dataloader的界面:
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应用程序(异步)和控制台应用程序(同步)。
答案 2 :(得分:0)
似乎没有必要简单地将方法标记为async而不使用await。将其标记为异步并不会使其异步,它允许您在方法体中使用等待(在await中执行的代码是异步发生的代码,然后异步方法的其余部分也将异步完成): / p>
通常,由async关键字修改的方法至少包含一个await表达式或语句。该方法同步运行,直到它到达第一个await表达式,此时它将被挂起,直到等待的任务完成。同时,控制权返回给方法的调用者。如果该方法不包含await表达式或语句,则它将同步执行。编译器警告会提醒您任何不包含等待的异步方法,因为这种情况可能表示错误。有关更多信息,请参阅编译器警告CS4014。
来自:async