C# 如何并行处理潜在的异步聚合任务,并在每个任务统一完成时对其进行处理

C# 如何并行处理潜在的异步聚合任务,并在每个任务统一完成时对其进行处理,c#,unity3d,parallel-processing,async-await,C#,Unity3d,Parallel Processing,Async Await,我陷入了一种情况,在“正常”的情况下,c#land可能会相对容易解决,但由于需要注意统一性,这就有点复杂了 最重要的是我有一组需要运行的任务,其中一些可以并行运行,一些是异步的,其他的不是。对于那些可以并行运行的任务,当每个任务完成时,我需要触发一个事件(在主线程中),让任何相关方知道任务已经完成 为了深入了解细节,这是针对Unity中的一个游戏,但是这个特定的代码要求它不能接触Unity API,因为它将在一个纯c#的单独项目中 从概念上讲,在我的游戏中,我有一个“日转换”。每晚玩家在睡觉时(

我陷入了一种情况,在“正常”的情况下,c#land可能会相对容易解决,但由于需要注意统一性,这就有点复杂了

最重要的是我有一组需要运行的任务,其中一些可以并行运行,一些是异步的,其他的不是。对于那些可以并行运行的任务,当每个任务完成时,我需要触发一个事件(在主线程中),让任何相关方知道任务已经完成

为了深入了解细节,这是针对Unity中的一个游戏,但是这个特定的代码要求它不能接触Unity API,因为它将在一个纯c#的单独项目中

从概念上讲,在我的游戏中,我有一个“日转换”。每晚玩家在睡觉时(或是用尽筋疲力尽或被敌人击倒),游戏将进入加载屏幕,处理更长的运行操作,我不想在游戏中间进行。例如运行模拟以更新日常经济状况,或将文件I/O加载到第二天要使用的内容中

这些take可能是线程安全的,也可能不是真正的异步调用(例如,文件加载)

我的目标是以一种尽可能少地阻塞主线程的方式来实现这一点(一些阻塞将会发生,这是不可避免的,因为一些任务可能会触及需要在主线程上发生的Unity API。这些不是线程安全的)

因为有些事情是异步的,所以我把所有事情都当作是可以等待的

My DayTransitioner类有一个IDayTransitionAction对象列表:

public interface IDayTransitionAction
{
    bool IsRepeating { get; }
    bool IsThreadSafe { get; }
    Task ProcessTransitionAsync();
}
这里确实需要两个截然不同的过程

第一个是线程安全的代码(异步或非异步)需要能够以实际并行运行的方式启动,并且当它们完成时,需要在主线程上触发一个事件

protected async Task ProcessThreadSafeTasks()
{
    //1st kick off in parallel, probably using one of the Task 
    //methods such as WhenAll or WhenAny or some other voodoo...
    //Not really sure what API calls or structure to use to do it...

    //As each comes back an event needs to be fired. 
    //The event needs to occur on the main thread.
    //this.OnIndividualItemProcessed();
}
第二,非线程安全的东西需要运行并等待(我知道这个组中的同步进程将阻塞主线程,但如果一个进程是真正异步的,它就不应该阻塞主线程)

第二种情况实际上是最容易解决的,因为Unity提供了一个同步上下文,强制在主线程上执行。因此,我认为后者可以在一个循环中等待

protected async Task ProcessNonThreadSafeTasks()
{
    foreach (var actionItem in this.nonThreadSafeActions)
    {
        await actionItem.ProcessTransitionAsync();

        //Raise the OnIndividualItemProcessed event so eventually
        //a loading bar can be filled in as tasks complete, though
        //this class doesn't know or care about the loading bar
        //specifically. Just that interested parties want to know
        //when an individual task is completed
        this.OnIndividualItemProcessed();
    }
}
这是第一个让我头疼的令人讨厌的案例,因为我真的不知道如何启动它们,使它们能够并行运行,同时在每个单独的事件完成时发送事件(特别是考虑到Unity的同步上下文…我想我必须暂时以某种方式覆盖它)。我将如何着手实现这一点

提前谢谢

另外,为了便于参考,将调用上述两个函数的方法

public async Task ProcessTransitionActions()
{
    //Used primarily to lock down the lists while processing is ongoing.
    //The Add method will dump anything that's attempting to be stored
    //to another list to be sorted and added to the main lists after processing
    //is finished.
    IsProcessing = true;

    await this.ProcessThreadSafeTasks();
    await this.ProcessNonThreadSafeTasks();

    //Purge any actions that aren't repeating.
    this.CleanNonRepeatingActions();
    //Add any actions that were stored while processing to the main lists.
    this.SortStoredActions();

    IsProcessing = false;

    //Send out an event to allow interested parties to know processing is
    //finished.
    this.OnProcessingCompleted();
}
编辑1*


只是为了澄清,“线程安全”操作旨在在其他线程(真正的并行)上运行(或至少能够运行),而不是简单地在主线程上并发运行。当它们完成时,我需要拍摄事件,该事件特别需要在主线程上播放。

您可能需要这样的内容

protected async Task ProcessThreadSafeTasks()
{
    //1st kick off in parallel, probably using one of the Task 
    //methods such as WhenAll or WhenAny or some other voodoo...
    //Not really sure what API calls or structure to use to do it...

    //As each comes back an event needs to be fired. 
    //The event needs to occur on the main thread.
    //this.OnIndividualItemProcessed();
}
如果你有几千个,效率不是特别高,但是如果你有几十个,效果应该很好

class TransitionRunner
{
    readonly List<IDayTransitionAction> list = new List<IDayTransitionAction>();
    readonly HashSet<Task> runningTasks = new HashSet<Task>();

    public async Task runAll()
    {
        Debug.Assert( 0 == runningTasks.Count );

        foreach( var dta in list )
        {
            if( dta.IsThreadSafe )
            {
                // Thread safe tasks are started and finished on the thread pool's threads
                Func<Task> fnRun = dta.ProcessTransitionAsync;
                runningTasks.Add( Task.Run( fnRun ) );
            }
            else
            {
                // Non-thread safe tasks are started and finished on the GUI thread.
                // If they're not actually async, the following line will run the complete task.
                Task task  = dta.ProcessTransitionAsync();
                if( task.IsCompleted )
                {
                    // It was a blocking task without await
                    // Or maybe await that completed extremely fast.
                    if( task.IsCompletedSuccessfully )
                        OnIndividualItemProcessed();
                    else
                        await task; // Propagate exception
                }
                else
                    runningTasks.Add( task );
            }
        }

        while( runningTasks.Count > 0 )
        {
            runningTasks.Remove( await Task.WhenAny( runningTasks ) );
            OnIndividualItemProcessed();
        }
    }
}
类转换运行程序
{
只读列表=新列表();
readonly HashSet runningTasks=new HashSet();
公共异步任务runAll()
{
Assert(0==runningTasks.Count);
foreach(列表中的var dta)
{
if(dta.IsThreadSafe)
{
//线程安全任务在线程池的线程上启动和完成
Func fnRun=dta.ProcessTransitionAsync;
runningTasks.Add(Task.Run(fnRun));
}
其他的
{
//非线程安全任务在GUI线程上启动和完成。
//如果它们实际上不是异步的,那么下面一行将运行完整的任务。
Task Task=dta.ProcessTransitionAsync();
如果(task.IsCompleted)
{
//这是一项迫不及待的阻拦任务
//或者,也许等待它以极快的速度完成。
如果(task.IsCompletedSuccessfully)
onIndividualTemprocessed();
其他的
等待任务;//传播异常
}
其他的
runningTasks.Add(任务);
}
}
while(runningTasks.Count>0)
{
runningTasks.Remove(wait Task.WhenAny(runningTasks));
onIndividualTemprocessed();
}
}
}

您可能需要这样的东西

如果你有几千个,效率不是特别高,但是如果你有几十个,效果应该很好

class TransitionRunner
{
    readonly List<IDayTransitionAction> list = new List<IDayTransitionAction>();
    readonly HashSet<Task> runningTasks = new HashSet<Task>();

    public async Task runAll()
    {
        Debug.Assert( 0 == runningTasks.Count );

        foreach( var dta in list )
        {
            if( dta.IsThreadSafe )
            {
                // Thread safe tasks are started and finished on the thread pool's threads
                Func<Task> fnRun = dta.ProcessTransitionAsync;
                runningTasks.Add( Task.Run( fnRun ) );
            }
            else
            {
                // Non-thread safe tasks are started and finished on the GUI thread.
                // If they're not actually async, the following line will run the complete task.
                Task task  = dta.ProcessTransitionAsync();
                if( task.IsCompleted )
                {
                    // It was a blocking task without await
                    // Or maybe await that completed extremely fast.
                    if( task.IsCompletedSuccessfully )
                        OnIndividualItemProcessed();
                    else
                        await task; // Propagate exception
                }
                else
                    runningTasks.Add( task );
            }
        }

        while( runningTasks.Count > 0 )
        {
            runningTasks.Remove( await Task.WhenAny( runningTasks ) );
            OnIndividualItemProcessed();
        }
    }
}
类转换运行程序
{
只读列表=新列表();
readonly HashSet runningTasks=new HashSet();
公共异步任务runAll()
{
Assert(0==runningTasks.Count);
foreach(列表中的var dta)
{
if(dta.IsThreadSafe)
{
//线程安全任务在线程池的线程上启动和完成
Func fnRun=dta.ProcessTransitionAsync;
runningTasks.Add(Task.Run(fnRun));
}
其他的
{
//非线程安全任务在GUI线程上启动和完成。
//如果它们实际上不是异步的,那么下面一行将运行完整的任务。
Task Task=dta.ProcessTransitionAsync();
如果(task.IsCompleted)
{
//这是一项迫不及待的阻拦任务
//或者等着那个com
private async Task ProcessActionAndRaiseEvent(ActionItem actionItem)
{
  await Task.Run(() => actionItem.ProcessTransitionAsync());
  this.OnIndividualItemProcessed();  
}