Создайте IAsyncEnumerable, используя TaskCompletionSource - PullRequest
1 голос
/ 30 апреля 2020

У меня есть метод, который принимает IEnumerable и возвращает его преобразованным с использованием оператора yield. Чтобы преобразовать один элемент перечислимого, мне нужно сначала узнать значение другого элемента. Поэтому я подумал об использовании TaskCompletionSource для создания чего-то вроде «обещания».

Проблема здесь в том, что этот код приводит к тупику, если значение, отличное от «a», является значением первого TestFieldA. Одним из решений было бы заказать перечислимое перед передачей его в метод - в этом случае вообще не требуется TaskCompletionSource. Я хотел бы знать, однако, если это можно сделать без этого. Я также знаю, что это можно сделать с некоторыми запросами LINQ, но для этого потребуется несколько раз перечислить ввод, чего я бы хотел избежать.

Это то, чего я пытаюсь достичь. (Работает только если первый TestFieldA == "a")

class Test
{
    public string TestFieldA {get;set;}
    public int TestFieldB {get;set;}
}


private async IAsyncEnumerable<Test> Transform(IEnumerable<Test> inputEnumerable)
{
    var tcs = new TaskCompletionSource<int>();

    foreach(var input in inputEnumerable)
    {
        if (input.TestFieldA == "a")
        {
            tcs.SetResult(input.TestFieldB);
            yield return input;
        }
        else
        {
            input.TestFieldB -= await tcs.Task;
            yield return input;
        }
    }
}

Ответы [ 2 ]

2 голосов
/ 30 апреля 2020

Ваш текущий план, кажется, зависит от возможности путешествовать во времени. Я бы посоветовал просто хранить неподходящие элементы в очереди (а не выдавать их), пока не найдете подходящий элемент со значением TestFieldA.

В этот момент вы удаляете все элементы из очереди, используйте команду now найдено значение и доходность каждого по очереди. Затем выдайте предмет с желаемым значением TestFieldA.

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

Здесь нет необходимости Task (CompletionSource), async или IAsyncEnumerable - вы не можете ничего выдать, пока не найдете свой a значение - если у вас нет доступа к машине времени.


Имейте также в виду, что итераторы зависят от того, чтобы вызывающие их лица неоднократно запрашивали новые элементы для продвижения вперед - вы останавливаетесь на каждом yield пока они не делают. Таким образом, было бы крайне рискованно рассмотреть возможность попробовать yield предметов рано, если у полученных предметов есть что-то "Task подобное"; Вызывающий абонент может решить await на одном, а не продолжать перечисление, в котором вы нуждаетесь.

1 голос
/ 30 апреля 2020

Идея может состоять в том, чтобы возвращать множество задач вместо IAsyncEnumerable. Примерно так:

private IEnumerable<Task<Test>> Transform(IEnumerable<Test> source)
{
    var tcs = new TaskCompletionSource<int>(
        TaskCreationOptions.RunContinuationsAsynchronously);

    foreach (var item in source)
    {
        if (item.TestFieldA == "a")
        {
            tcs.TrySetResult(item.TestFieldB);
        }
        yield return TransformItemAsync(item);
    }

    async Task<Test> TransformItemAsync(Test input)
    {
        var value = await tcs.Task.ConfigureAwait(false);
        input.TestFieldB -= value;
        return input;
    }
}

Это все равно создаст проблему взаимоблокировки, если вызывающий попытается ожидать каждую задачу по очереди. Для решения этой проблемы у вызывающей стороны должен быть способ как-то дождаться выполнения задач в порядке их завершения. В библиотеке Nito.AsyncEx Стивена Клири есть нечто подобное, метод расширения OrderByCompletion:

// Creates a new collection of tasks that complete in order.
public static List<Task<T>> OrderByCompletion<T>(this IEnumerable<Task<T>> @this);

Вы также можете получить исходный код из здесь если хотите.

...