правильное использование ассинхронности
Подскажите, пожалуйста, какой из методов более правильный и почему? У меня в синхронном цикле перебирается 5000 объектов. Сначала идут вычисления, затем действия по сети и в конце запись в текстовый лог.
Мне необходимо записать в текстовый лог информацию, но чтобы это вставало в очередь TAP каждый раз в самый конец, т.к. логирование не самое важное на текущий момент.
void Log(string text)
{
_ = File.AppendAllTextAsync($"\\logs\\{mail}", text);
}
void Log2(string text)
{
Task.Run(()=>File.AppendAllText($"\\logs\\{mail}", text));
}
async Task Log3(string text)
{
await File.AppendAllTextAsync($"\\logs\\{mail}", text);
}
Благодарю заранее за любые подсказки.
Ответы (1 шт):
async Task Log3(string text)
{
await File.AppendAllTextAsync($"\\logs\\{mail}", text);
}
А можно ли вызывать его не как
await Log3(text), а просто_ = Log3(text)?
Вызвать то конечно можно, вам нужен режим запуска "запустил и забыл", но что будет с файлом, если одновременно вы вызвали этот метод, скажем, 100 раз? Возникнет исключение при поытке открыть на запись уже открытый файл, которое просто пропадет в бездну, уронит поток, и всё, ну потому что вы его никак не обрабатываете и никак не синхронизируете доступ к файлу из разных потоков. Выглядеть это будет как в лог что-то записалось, но не всё, что вы хотели туда записать. Плохой эффект.
Что же делать - нужно использовать блокировку. Вот например решение для многопоточного доступа без асинхронности.
Для вашего частного случая, можно поступить следующим образом - правильно использовать async void.
static readonly SemaphoreSlim _logSemaphore = new SemaphoreSlim(1);
async void Log4(string text)
{
await _logSemaphore.WaitAsync();
try
{
// только один поток в один момент времени может попасть в эту часть кода
await File.AppendAllTextAsync($"\\logs\\{mail}", text);
}
catch (Exception ex)
{
// ... ex.Message - ошибка при записи в лог
}
finally
{
_logSemaphore.Release();
}
}
Тогда можно просто вызвать
Log4("text");
И сообщение отправится в лог без ожидания окончания записи. Но как сказано выше в комментариях - лучше использовать готовые логгеры, а не изобретать свой.
Дисклеймер
Как вы и хотели, я сделал без ожидания, но есть побочный эффект, например идет запись в лог, и вы в этот момент закрываете прилижение. Вас устроит недозаписанный лог? Или падение приложения при закрытии? Или отсутствие последней записи в логе? Как вы дождетесь окончания записи в лог? Никак. Потому что async void нельзя ожидать. Рассмотрите это решение как пример и попробуйте его доработать так, чтобы ситуации, когда приложение закрылось недозаписать все логи, не возникло.
async void - опасно, плохо, и я не рекомендую это использовать без полного понимания, как это работает. _ = Log3 дает тот же самый эффект, что async void, то есть та же проблема может возникнуть.
Куда копать - шаблон проектирования Producer/Consumer и подходящая вам его реализация.