簡體   English   中英

C# LanguageExt - 將多個異步調用合並為一個分組調用

[英]C# LanguageExt - combine multiple async calls into one grouped call

我有一個方法可以從數據存儲中異步查找項目;

class MyThing {}
Task<Try<MyThing>> GetThing(int thingId) {...}

我想從數據存儲中查找多個項目,並編寫了一個新方法來執行此操作。 我還編寫了一個輔助方法,它將采用多個Try<T>並將它們的結果合並到一個Try<IEnumerable<T>>


public static class TryExtensions
{
    Try<IEnumerable<T>> Collapse<T>(this IEnumerable<Try<T>> items)
    {
        var failures = items.Fails().ToArray();
        return failures.Any() ?
            Try<IEnumerable<T>>(new AggregateException(failures)) :
            Try(items.Select(i => i.Succ(a => a).Fail(Enumerable.Empty<T>())));
    }
}

async Task<Try<MyThing[]>> GetThings(IEnumerable<string> ids)
{
    var results = new List<Try<Things>>();

    foreach (var id in ids)
    {
        var thing = await GetThing(id);
        results.Add(thing);
    }

    return results.Collapse().Map(p => p.ToArray());
}

另一種方法是這樣的;

async Task<Try<MyThing[]>> GetThings(IEnumerable<string> ids)
{
    var tasks = ids.Select(async id => await GetThing(id)).ToArray();
    await Task.WhenAll(tasks);
    return tasks.Select(t => t.Result).Collapse().Map(p => p.ToArray());
}

這樣做的問題是所有任務都將並行運行,我不想用大量並行請求來破壞我的數據存儲。 我真正想要的是使用LanguageExt monadic 原則和特性使我的代碼具有功能性。 有誰知道如何實現這一目標?


更新

感謝@MatthewWatson 的建議,這就是SemaphoreSlim樣子;

async Task<Try<MyThing[]>> GetThings(IEnumerable<string> ids)
{
    var mutex = new SemaphoreSlim(1);
    var results = ids.Select(async id =>
    {
        await mutex.WaitAsync();
        try { return await GetThing(id); }
        finally { mutex.Release(); }
    }).ToArray();
    await Task.WhenAll(tasks);
    return tasks.Select(t => t.Result).Collapse().Map(Enumerable.ToArray);
    return results.Collapse().Map(p => p.ToArray());
}

問題是,這仍然不是非常單一/功能性的,並且最終的代碼行數比我帶有foreach塊的原始代碼多。

在“另一種方式”中,當您致電時,您幾乎實現了目標:

var tasks = ids.Select(async id => await GetThing(id)).ToArray();

除了 Tasks 不會按順序運行,因此您最終會遇到許多查詢您的數據存儲的查詢,這是由.ToArray()Task.WhenAll引起的。 一旦你調用了.ToArray()它就已經分配並啟動了任務,所以如果你可以“容忍”一個foreach來實現順序任務的運行,就像這樣:

public static class TaskExtensions
{
    public static async Task RunSequentially<T>(this IEnumerable<Task<T>> tasks)
    {
        foreach (var task in tasks) await task;
    }
}

盡管運行查詢的“循環”通常不是一個很好的做法,除非您有一些后台服務和一些特殊場景,否則通過WHERE thingId IN (...)將其用於數據庫引擎通常會更好選項。 即使您有大量的 thingId,我們也可以將其切成小 10、100……以縮小WHERE IN足跡。

回到我們RunSequentially ,我使更多的功能像這樣的例子:

tasks.ToList().ForEach(async task => await task);

但遺憾的是,這仍然會運行有點“並行”的任務。

所以最終的用法應該是:

async Task<Try<MyThing[]>> GetThings(IEnumerable<string> ids)
{
    var tasks = ids.Select(id => GetThing(id));// remember don't use .ToArray or ToList...
    await tasks.RunSequentially();
    return tasks.Select(t => t.Result).Collapse().Map(p => p.ToArray());
}

另一個矯枉過正的功能解決方案是遞歸地隊列中獲得懶惰!!

取而代之的是GetThing ,獲得一個 Lazy one GetLazyThing ,只需通過包裝GetThing即可返回Lazy<Task<Try<MyThing>>>

new Lazy<Task<Try<MyThing>>>(() => GetThing(id))

現在使用幾個擴展/功能:

public static async Task RecRunSequentially<T>(this IEnumerable<Lazy<Task<T>>> tasks)
{
    var queue = tasks.EnqueueAll();
    await RunQueue(queue);
}

public static Queue<T> EnqueueAll<T>(this IEnumerable<T> list)
{
    var queue = new Queue<T>();
    list.ToList().ForEach(m => queue.Enqueue(m));
    return queue;
}

public static async Task RunQueue<T>(Queue<Lazy<Task<T>>> queue)
{
    if (queue.Count > 0)
    {
        var task = queue.Dequeue();
        await task.Value; // this unwraps the Lazy object content
        await RunQueue(queue);
    }
}

最后:

var lazyTasks = ids.Select(id => GetLazyThing(id));
await lazyTasks.RecRunSequentially();
// Now collapse and map as you like

更新

但是,如果您不喜歡EnqueueAllRunQueue不是“純”這一事實,我們可以使用相同的Lazy技巧采用以下方法

public static async Task AwaitSequentially<T>(this Lazy<Task<T>>[] array, int index = 0)
{
    if (array == null || index < 0 || index >= array.Length - 1) return;
    await array[index].Value;
    await AwaitSequentially(array, index + 1); // ++index is not pure :)
}

現在:

var lazyTasks = ids.Select(id => GetLazyThing(id));
await tasks.ToArray().AwaitSequentially();
// Now collapse and map as you like

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM