Может ли этот асинхронный код завершиться не по порядку?

#c# #asp.net-web-api #async-await

Вопрос:

У меня есть следующий код C# в контроллере AspNet WebAPI:

 private static async Task<string> SaveDocumentAsync(HttpContent content) {
    var path = "something";
    using (var file = File.OpenWrite(path)) {
        await content.CopyToAsync(file);
    }
    return path;
}

public async Task<IHttpActionResult> Put() {
    var path = await SaveDocumentAsync(Request.Content);
    await SaveDbRecordAsync(path); // writes something to the database using System.Data and awaiting Async methods
    return OK();
}
 

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

Чтобы прояснить, как я это наблюдаю. Это приложение, которое считывает путь из базы данных, а затем пытается прочитать файл и обнаруживает, что его там нет. Файл появляется вскоре после этого. Это происходит не каждый раз, обычно файл появляется первым. Может быть, 1 из 1000 это происходит не так.

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

Комментарии:

1. I am sometimes seeing the database record visible before the document has finished being written. — Вероятно, обозреватель решений просто обновляется позже, чем ваша база данных. Файл есть, но он появляется с задержкой, так как он не индексируется через VS мгновенно

2. 1 к комментарию @gpanagopoulos. Если быть точным, я видел File.Move() возвращение до завершения операции на докер-Альпине. Переключение базового образа с alpine на debian устранило проблему. Это было в .NET Core 2.2.

3. @Silvermind Изначально я делал что-то подобное, но я думал, что устранил это, прежде чем задавать вопрос. Я использую общий ресурс SMB. Возможно, это артефакт кэширования клиента, но если это так, то это странно, потому что SMB имеет как положительный, так и отрицательный кэш. Потребитель не должен запрашивать файл до его записи, поэтому отрицательный кэш на этой машине никогда не должен заполняться.

4. Не могли бы вы попробовать избавиться HttpContent от него после того, как закончите с ним, чтобы посмотреть, имеет ли это какое-либо значение?

5. @Теодорзулиас, я попробую это сделать. Это займет некоторое время, так как в настоящее время это проблема только для живых…

Ответ №1:

Поскольку вы await SaveDocumentAsync вводите функцию перед вызовом SaveDbRecordAsync , она выполняется после SaveDocumentAsync завершения.

Если бы вы выполняли задачи параллельно, то await они:

 var saveTask = SaveDocumentAsync(Request.Content);
var dbTask = SaveDbRecordAsync("a/path.ext");

await saveTask;
await dbTask;
 

тогда вы не смогли бы гарантировать выполнение заказа.

@Neiston затрагивает важный момент: возможно, приложение, которое вы используете для просмотра результатов, обновляется с задержкой и заставляет вас думать, что порядок переключен.

Комментарии:

1. Откуда path он взялся здесь?

2. Это опечатка из копипаста.

3. Это на самом деле не имеет смысла в качестве примера сейчас, потому SaveDbRecordAsync что зависит от результата SaveDocumentAsync .

4. Правда. Мне следовало бы привести другой пример. Дело в том, что не должно быть невыполнения заказа.

Ответ №2:

Поскольку вы пишете в 2 разных файла (один файл, одна база данных), то операционная система идеально подходит для выполнения операций записи в любом порядке, который «лучше всего» подходит для носителя данных.

В старые времена вращающегося хранилища 2 запроса находились бы в очереди записи, и если бы головки r/w в настоящее время были ближе к дорожкам для базы данных, чем файл, то ОС (или, возможно, контроллер жесткого диска) сначала записала бы данные базы данных, а затем данные файла.

Это предполагает, что и ваш файл, и сервер базы данных запущены на одной и той же физической машине. Если вы пишете в общую папку и/или сервер БД также находится на другой машине, то кто знает, в каком порядке они закончат.

Комментарии:

1. Однако будут ли задачи выполняться в другом порядке? Как только первая задача ожидается и завершена, выполняется продолжение (остальная часть метода), затем запускается вторая задача.

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

3. @Neil блок использования вокруг записи файла включает в себя сброс и закрытие. Я наблюдаю за файлом через API файловой системы, а не просматриваю байты на диске. Поэтому, хотя ваш ответ верен, он не объясняет того, что я вижу.