Удалите метод async и ожидайте с помощью await foreach

Я хотел бы знать, можно ли убрать ожидание из этого метода, чтобы не было асинхронного метода.

public static async Task Run(IMinioClient minio,
        string bucketName = "my-bucket-name",
        string prefix = null,
        bool recursive = true,
        bool versions = false)
    {
        try
        {
            Console.WriteLine("Running example for API: ListObjectsAsync");
            var listArgs = new ListObjectsArgs()
                .WithBucket(bucketName)
                .WithPrefix(prefix)
                .WithRecursive(recursive)
                .WithVersions(versions);
            await foreach (var item in minio.ListObjectsEnumAsync(listArgs).ConfigureAwait(false))
                Console.WriteLine($"Object: {item.Key}");
            Console.WriteLine($"Listed all objects in bucket {bucketName}\n");
        }
        catch (Exception e)
        {
            Console.WriteLine($"[Bucket]  Exception: {e}");
        }
    }

https://github.com/minio/minio-dotnet/blob/master/Minio.Examples/Cases/ListObjects.cs

Спасибо за помощь.

Обычно для этого я использую .Result, но здесь ожидание выполняется по foreach.

Вы звоните ListObjectsEnumAsync. Существует ли также метод ListObjectsEnum, который возвращает IEnumerable вместо IAsyncEnumerable?

SomeBody 21.08.2024 10:13

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

MakePeaceGreatAgain 21.08.2024 10:23

вам нужно переосмыслить это; вот соответствующее обсуждение - речь идет не конкретно о .NET, а об асинхронных и неасинхронных методах (тема, которая влияет на многие среды выполнения/языки) - Journal.stuffwithstuff.com/2015/02/01/… - асинхронный код принципиально отличается, и вы не можете просто отмахнуться от него и представить его неасинхронным способом.

Marc Gravell 21.08.2024 10:28

Клиент Minio, предположительно, как и любой S3-совместимый клиент для .Net, который вы могли бы использовать вместо него, написан на асинхронном уровне. Зачем бороться с этим, а не просто использовать? Попытка использовать асинхронные вещи способом синхронизации часто приводит к трудно выявляемым проблемам.

Ralf 21.08.2024 10:29

Также вы связали источник, просто посмотрите предыдущую регистрацию этого файла/метода в git. В его подписи не было Task. Возможно, эта версия все еще работает с неизмененным в остальном клиентом minio.

Ralf 21.08.2024 10:38

Проблема XY?

Riwen 21.08.2024 11:06

Вы можете использовать ToEnumerable() или ToBlockingEnumerable() для преобразования асинхронного перечислимого в перечисляемое. Смотрите этот ответ.

John Wu 21.08.2024 11:20
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
7
102
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Довольно странно просто не использовать асинхронное выполнение, когда это возможно, но я оставлю это на ваше усмотрение. Просто помните о рисках взаимоблокировок и снижения производительности.

Если вам действительно не нужно использовать await, вот реализация без него. Немного многословно и не так чисто, как с await, но вот оно.

Во-первых, давайте определим пример метода, возвращающего IAsyncEnumerable:

public static class TestClass
{
    public static async IAsyncEnumerable<int> GetIntsAsync()
    {
        for (int i = 0; i < 10; i++)
        {
            await Task.Delay(100);
            yield return i;
        }
    }
}

и реализация без await будет:

var asyncEnumerator = TestClass.GetIntsAsync().GetAsyncEnumerator();
try
{
    while (asyncEnumerator.MoveNextAsync().AsTask().GetAwaiter().GetResult())
    {
        Console.WriteLine(asyncEnumerator.Current);
    }
}
finally
{
    asyncEnumerator?.DisposeAsync().AsTask().GetAwaiter().GetResult();
}

Для удобства вы можете определить методы расширения для таких сценариев:

public static class Extensions
{
    public static T AwaitSynchornously<T>(this ValueTask<T> valueTask)
    {
        return valueTask.AsTask().GetAwaiter().GetResult();
    }

    public static void AwaitSynchornously(this ValueTask valueTask)
    {
        valueTask.AsTask().GetAwaiter().GetResult();
    }

    public static T[] LoopSynchronously<T>(
        this IAsyncEnumerable<T> asyncEnumerable,
        Action<T> action)
    {
        return asyncEnumerable.EnumerateSynchronously(action).ToArray();
    }

    public static IEnumerable<T> EnumerateSynchronously<T>(
        this IAsyncEnumerable<T> asyncEnumerable,
        Action<T> action)
    {
        var asyncEnumerator = asyncEnumerable.GetAsyncEnumerator();
        try
        {
            while (asyncEnumerator.MoveNextAsync().AwaitSynchornously())
            {
                action(asyncEnumerator.Current);
                yield return asyncEnumerator.Current;
            }
        }
        finally
        {
            asyncEnumerator?.DisposeAsync().AwaitSynchornously();
        }
    }
}

Использование тогда очень просто:

TestClass.GetIntsAsync().LoopSynchronously(Console.WriteLine);

и использование valueTask.AsTask() в цикле, даже если это не приведет к тупику, приведет к чрезмерному выделению GC.

Dai 21.08.2024 11:05

@Дай, как думаешь, можно обойтись без AsTask()?

Ivan Petrov 21.08.2024 11:07

Я бы просто сделал один метод расширения для IAsyncEnumerable без Action

Ivan Petrov 21.08.2024 11:09

@Dai Ваш комментарий действителен, но ОП специально просил такое решение (альтернативой является использование Result, но оно имеет те же риски). Вот почему я упомянул, что это странный поступок... Я обновлю свой ответ вашим предложением, хотя думаю, что ОП осознает риск, на который он идет. Более того, невозможно синхронно ждать ValueTask без AsTask — опять же, это необходимо для выполнения того, что нужно OP. Я оставляю внимание ему

Michał Turczyn 21.08.2024 11:21

@IvanPetrov Это был всего лишь пример, я оставлю ОП решать, нужно ли ему использовать Action или, может быть, Func, а может быть, вообще ничего :)

Michał Turczyn 21.08.2024 11:24

@MichałTurczyn почти искушает меня опубликовать ответ, который я написал, прежде чем увидеть ваш :)

Ivan Petrov 21.08.2024 11:37
Ответ принят как подходящий

Вы можете использовать API .NET 7 ToBlockingEnumerable:

Преобразует экземпляр IAsyncEnumerable<T> в экземпляр IEnumerable<T>, который перечисляет элементы блочным способом.

Пример использования:

public static void Run(IMinioClient minio,
    string bucketName = "my-bucket-name",
    string prefix = null,
    bool recursive = true,
    bool versions = false)
{
    var listArgs = new ListObjectsArgs()
        .WithBucket(bucketName)
        .WithPrefix(prefix)
        .WithRecursive(recursive)
        .WithVersions(versions);

    foreach (var item in minio.ListObjectsEnumAsync(listArgs).ToBlockingEnumerable())
    {
        Console.WriteLine($"Object: {item.Key}");
    }
}

Это решение подвержено взаимоблокировкам в зависимости от реализации метода ListObjectsEnumAsync. Блокировка по асинхронному коду вообще нецелесообразна.

Вы уверены, что это конкретное решение подвержено взаимоблокировкам - в документации ничего не упоминается, и мы фактически «ждем», используя ту же инфраструктуру с частным ManualResetEventWithAwaiterSupport классом

Ivan Petrov 21.08.2024 11:55

@IvanPetrov да, я почти уверен. Если ListObjectsEnumAsync реализован с помощью async/await, и есть хотя бы один await без ConfigureAwait(false), и код вызывается в потоке пользовательского интерфейса приложения WinForms или WPF, взаимоблокировка почти гарантирована.

Theodor Zoulias 21.08.2024 12:12

Другие вопросы по теме