Question

I'm writing a C# .Net 4.5 library for doing common sql database operations (backup, restore, execute script, etc.). I want to have both synchronous and asynchronous functions for each operation, as this library will be used by both console and GUI apps, but I don't want to duplicate code everywhere. So as I see it, I have two options:

  1. Write the code that does the work in a synchronous function, and then just wrap it in a task for the async function, like so:

    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. Write the code that does the work in an asynchronous function, and call it from a synchronous function using .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.  
    }
    

Is one option better than the other? Is one a best practice? Or are there any other (better) alternatives?

I know that one caveat to using .Wait() is that all of the await statements in the async function have to use .ConfigureAwait(false) to avoid deadlocks (as discussed here), but since I'm writing a library that will never need to access the UI or WebContext I am safe to do that.

I'll note too that the SQL library typically also has both synchronous and async functions that can be used, so if doing the work in the sync function, I would call their sync function, and if doing the work in the async function, I would call their async function.

Thoughts/suggestions are appreciated.

-- edit: I've also posted this question on the MSDN forums here to try and get an official MS response --

Was it helpful?

Solution

I want to have both synchronous and asynchronous functions for each operation, as this library will be used by both console and GUI apps, but I don't want to duplicate code everywhere.

The best answer is: don't.

Stephen Toub has two excellent blog posts on this topic:

He recommends exposing asynchronous methods as asynchronous, and synchronous methods as synchronous. If you need to expose both, then encapsulate common functionality in private (synchronous) methods, and duplicate the async/sync differences.

OTHER TIPS

I had a similar situation where some applications needed the data to be loaded synchronously and others asyc. I decided to created an interface that I called my dataloader:

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

The AssignProjects callback is just a simple delegate that takes in the returned list of projects:

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

Now, the beauty of this is that you can work with the interface without knowing whether you are dealling in sync or async.

Three classes are created: one base, one sync, and one async:

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

Now, in your application, you can decide how you want to load data...this could be injected through an IoC container, but is hard coded for demo purposes:

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

Now, your calling code looks the same and is none the wiser to whether it is async or sync:

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

I use this regularly for unit testing (sync), WPF applications (async), and console applications (sync).

There does not seem to be a point to simply mark a method as async without using an await. Marking it as async does not make it asynchronous, it allows you to use awaits (the code executed in the await is what happens asynchronously, and then the rest of the async method will also be done asynchronously) in the body of the method:

Typically, a method modified by the async keyword contains at least one await expression or statement. The method runs synchronously until it reaches the first await expression, at which point it is suspended until the awaited task is complete. In the meantime, control is returned to the caller of the method. If the method does not contain an await expression or statement, then it executes synchronously. A compiler warning alerts you to any async methods that don't contain await because that situation might indicate an error. For more information, see Compiler Warning CS4014.

From: async

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top