С# имеют синхронную функцию вызова функции асинхронной функции или функцию синхронной функции вызова async

Я пишу библиотеку С#.Net 4.5 для выполнения общих операций с базами данных sql (резервное копирование, восстановление, выполнение script и т.д.). Я хочу иметь как синхронные, так и асинхронные функции для каждой операции, так как эта библиотека будет использоваться как консольными, так и графическими приложениями, но я не хочу дублировать код везде. Так что, как я вижу, у меня есть два варианта:

  • Напишите код, который выполняет работу в синхронной функции, а затем просто заверните его в задачу для функции 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 должны использовать .ConfigureAwait(false), чтобы избежать взаимоблокировок (как обсуждалось здесь), но поскольку я пишу библиотеку, которая никогда не будет нуждаться в доступе к пользовательскому интерфейсу или WebContext, я могу это сделать.

Я также отмечу, что библиотека SQL обычно также имеет как синхронные, так и асинхронные функции, которые можно использовать, поэтому, если вы выполняете работу в функции синхронизации, я бы назвал их функцию синхронизации и выполнил работу в async функции, я бы назвал их асинхронной функцией.

Мысли/предложения приветствуются.

- edit: Я также разместил этот вопрос на форумах MSDN здесь, чтобы попытаться получить официальный ответ MS -

Ответы

Ответ 1

Я хочу иметь как синхронные, так и асинхронные функции для каждой операции, так как эта библиотека будет использоваться как консольными, так и графическими приложениями, но я не хочу дублировать код везде.

Лучший ответ: do not.

У Стивена Туба есть две отличные сообщения в блоге по этой теме:

Он рекомендует разоблачать асинхронные методы как асинхронные, а синхронные - синхронно. Если вам нужно разоблачить и то, и другое, инкапсулируйте общую функциональность в частные (синхронные) методы и дублируйте различия по асинхронному/синхронному.

Ответ 2

У меня была аналогичная ситуация, когда некоторые приложения нуждались в загрузке данных синхронно, а другие 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);
            });
    }

Я использую это регулярно для модульного тестирования (sync), приложений WPF (async) и консольных приложений (sync).

Ответ 3

Кажется, нет смысла просто отмечать метод async без использования ожидания. Маркировка как async не делает его асинхронным, он позволяет использовать ожидания (код, выполняемый в ожидании, происходит асинхронно, а затем остальные асинхронные методы также будут выполняться асинхронно) в теле метода:

Как правило, метод, модифицированный ключевым словом async, содержит хотя бы одно выражение или выражение ожидания. Метод выполняется синхронно до тех пор, пока он не достигнет первого ожидающего выражения, после чего он будет приостановлен до завершения ожидаемой задачи. Тем временем управление возвращается вызывающей стороне метода. Если метод не содержит выражения ожидания или оператора ожидания, он выполняется синхронно. Предупреждение компилятора предупреждает вас о любых асинхронных методах, которые не содержат ожидания, потому что эта ситуация может указывать на ошибку. Для получения дополнительной информации см. Предупреждение компилятора CS4014.

От: async