如何使用C#在后台将要串行执行的委托排队?

如何使用C#在后台将要串行执行的委托排队?,c#,.net,multithreading,task,backgroundworker,C#,.net,Multithreading,Task,Backgroundworker,从游戏循环开始,我想在后台开始工作,应该一个接一个地执行,但不应该阻止游戏循环 理想情况下,类BackgroundQueue可以这样使用: BackgroundQueue myQueue = new BackgroundQueue(); //game loop called 60 times per second void Update() { if (somethingHappens) { myQueue.Enqueue(() => MethodThatT

从游戏循环开始,我想在后台开始工作,应该一个接一个地执行,但不应该阻止游戏循环

理想情况下,类
BackgroundQueue
可以这样使用:

BackgroundQueue myQueue = new BackgroundQueue();
//game loop called 60 times per second
void Update()
{
    if (somethingHappens)
    {
        myQueue.Enqueue(() => MethodThatTakesLong(someArguments))
    }   
}
在.NET中是否有一个现成的类适用于该场景?或者有人知道如何实现
BackgroundQueue
类的好例子吗


如果类可以报告当前是否正在做某事以及排队的代理数,那么这将是一个很好的选择

一个解决方案来自优秀的团队。在他们关于基本结构的部分中,作者几乎完全符合您的要求

按照该链接向下滚动到生产者/消费者队列

在后面的一节中,他指出,虽然它也可以正常工作,但除了在高度并发的场景中,它在所有情况下的性能都会更差。但是对于低负载的情况,最好是让某些东西轻松工作。我对文件中的索赔没有个人经验,但可以由你来评估

我希望这有帮助

Edit2:根据Evk的建议(谢谢!),这门课看起来就像你想要的。默认情况下,它在引擎盖下使用ConcurrentQueue。我特别喜欢这个方法,以及使用CancellationTokens的能力。当事情阻塞时,“关机”场景并不总是正确的,但在我看来这是正确的

编辑3:根据要求,这是一个如何与BlockingCollection一起工作的示例。我使用了
foreach
getconsumineGenumerable
使问题的消费者端更加紧凑:

using System.Collections.Concurrent;
private static void testMethod()
{
  BlockingCollection<Action> myActionQueue = new BlockingCollection<Action>();
  var consumer = Task.Run(() =>
  {
    foreach(var item in myActionQueue.GetConsumingEnumerable())
    {
      item(); // Run the task
    }// Exits when the BlockingCollection is marked for no more actions
  });

  // Add some tasks
  for(int i = 0; i < 10; ++i)
  {
    int captured = i; // Imporant to copy this value or else
    myActionQueue.Add(() =>
    {
      Console.WriteLine("Action number " + captured + " executing.");
      Thread.Sleep(100);  // Busy work
      Console.WriteLine("Completed.");
    });
    Console.WriteLine("Added job number " + i);
    Thread.Sleep(50);
  }
  myActionQueue.CompleteAdding();
  Console.WriteLine("Completed adding tasks.  Waiting for consumer completion");

  consumer.Wait();  // Waits for consumer to finish
  Console.WriteLine("All actions completed.");
}
使用System.Collections.Concurrent;
私有静态void testMethod()
{
BlockingCollection myActionQueue=新建BlockingCollection();
var consumer=Task.Run(()=>
{
foreach(myActionQueue.GetConsumingEnumerable()中的变量项)
{
item();//运行任务
}//当BlockingCollection标记为不再执行任何操作时退出
});
//添加一些任务
对于(int i=0;i<10;++i)
{
int captured=i;//复制此值或
myActionQueue.Add(()=>
{
Console.WriteLine(“操作编号”+捕获+“执行”);
线程。睡眠(100);//繁忙的工作
Console.WriteLine(“已完成”);
});
Console.WriteLine(“添加的作业编号”+i);
睡眠(50);
}
myActionQueue.CompleteAdding();
Console.WriteLine(“已完成添加任务。正在等待使用者完成”);
consumer.Wait();//等待consumer完成
Console.WriteLine(“所有操作已完成”);
}

我在Sleep()调用中添加了一些内容,这样您就可以看到在添加其他内容的同时也添加了一些内容。您还可以选择启动任意数量的
消费者
lambda(只需将其称为
操作
,然后多次启动
操作
)或添加循环。您可以随时对集合调用
Count
,以获取未运行的任务数。假设这不是零,那么您的生产者任务正在运行。

一个解决方案是来自卓越。在他们关于基本结构的部分中,作者几乎完全符合您的要求

按照该链接向下滚动到生产者/消费者队列

在后面的一节中,他指出,虽然它也可以正常工作,但除了在高度并发的场景中,它在所有情况下的性能都会更差。但是对于低负载的情况,最好是让某些东西轻松工作。我对文件中的索赔没有个人经验,但可以由你来评估

我希望这有帮助

Edit2:根据Evk的建议(谢谢!),这门课看起来就像你想要的。默认情况下,它在引擎盖下使用ConcurrentQueue。我特别喜欢这个方法,以及使用CancellationTokens的能力。当事情阻塞时,“关机”场景并不总是正确的,但在我看来这是正确的

编辑3:根据要求,这是一个如何与BlockingCollection一起工作的示例。我使用了
foreach
getconsumineGenumerable
使问题的消费者端更加紧凑:

using System.Collections.Concurrent;
private static void testMethod()
{
  BlockingCollection<Action> myActionQueue = new BlockingCollection<Action>();
  var consumer = Task.Run(() =>
  {
    foreach(var item in myActionQueue.GetConsumingEnumerable())
    {
      item(); // Run the task
    }// Exits when the BlockingCollection is marked for no more actions
  });

  // Add some tasks
  for(int i = 0; i < 10; ++i)
  {
    int captured = i; // Imporant to copy this value or else
    myActionQueue.Add(() =>
    {
      Console.WriteLine("Action number " + captured + " executing.");
      Thread.Sleep(100);  // Busy work
      Console.WriteLine("Completed.");
    });
    Console.WriteLine("Added job number " + i);
    Thread.Sleep(50);
  }
  myActionQueue.CompleteAdding();
  Console.WriteLine("Completed adding tasks.  Waiting for consumer completion");

  consumer.Wait();  // Waits for consumer to finish
  Console.WriteLine("All actions completed.");
}
使用System.Collections.Concurrent;
私有静态void testMethod()
{
BlockingCollection myActionQueue=新建BlockingCollection();
var consumer=Task.Run(()=>
{
foreach(myActionQueue.GetConsumingEnumerable()中的变量项)
{
item();//运行任务
}//当BlockingCollection标记为不再执行任何操作时退出
});
//添加一些任务
对于(int i=0;i<10;++i)
{
int captured=i;//复制此值或
myActionQueue.Add(()=>
{
Console.WriteLine(“操作编号”+捕获+“执行”);
线程。睡眠(100);//繁忙的工作
Console.WriteLine(“已完成”);
});
Console.WriteLine(“添加的作业编号”+i);
睡眠(50);
}
myActionQueue.CompleteAdding();
Console.WriteLine(“已完成添加任务。正在等待使用者完成”);
consumer.Wait();//等待consumer完成
Console.WriteLine(“所有操作已完成”);
}
我在Sleep()调用中添加了一些内容,这样您就可以看到在添加其他内容的同时也添加了一些内容。您还可以选择启动任意数量的
消费者
lambda(只需将其称为
操作
,然后多次启动
操作
)或添加循环。您可以随时对集合调用
Count
,以获取未运行的任务数。假设这是非零,那么您的生产者任务正在运行。

这个怎么样

void Main()
{
    var executor = new MyExecutor();
    executor.Execute(()=>Console.WriteLine("Hello"));
    executor.Execute(()=>Console.WriteLine(","));
    executor.Execute(()=>Console.WriteLine("World"));
}

public class MyExecutor
{
    private Task _current = Task.FromResult(0);

    public void Execute(Action action)
    {
        _current=_current.ContinueWith(prev=>action());
    }
}
UPD

更新代码。现在我们可以得到动作的数量,从不同线程推送,等等

void Main()
{
    var executor = new MyExecutor();
    executor.Execute(() => Console.WriteLine("Hello"));
    executor.Execute(() => Thread.Sleep(100));
    executor.Execute(() => Console.WriteLine(","));
    executor.Execute(() => { throw new Exception(); });
    executor.Execute(() => Console.WriteLine("World"));
    executor.Execute(() => Thread.Sleep(100));

    executor.WaitCurrent();

    Console.WriteLine($"{nameof(MyExecutor.Total)}:{executor.Total}");
    Console.WriteLine($"{nameof(MyExecutor.Finished)}:{executor.Finished}");
    Console.WriteLine($"{nameof(MyExecutor.Failed)}:{executor.Failed}");
}

public class MyExecutor
{
    private Task _current = Task.FromResult(0);
    private int _failed = 0;
    private int _finished = 0;
    private int _total = 0;
    private object _locker = new object();

    public void WaitCurrent()
    {
        _current.Wait();        
    }

    public int Total
    {
        get { return _total; }
    }

    public int Finished
    {
        get { return _finished; }
    }

    public int Failed
    {
        get { return _failed; }
    }

    public void Execute(Action action)
    {
        lock (_locker) // not sure that lock it is the best way here
        {
            _total++;
            _current = _current.ContinueWith(prev => SafeExecute(action));
        }
    }

    private void SafeExecute(Action action)
    {
        try
        {               
            action();
        }
        catch 
        {
            Interlocked.Increment(ref _failed);
        }
        finally 
        {
            Interlocked.Increment(ref _finished);
        }
    }
}
这个怎么样

void Main()
{
    var executor = new MyExecutor();
    executor.Execute(()=>Console.WriteLine("Hello"));
    executor.Execute(()=>Console.WriteLine(","));
    executor.Execute(()=>Console.WriteLine("World"));
}

public class MyExecutor
{
    private Task _current = Task.FromResult(0);

    public void Execute(Action action)
    {
        _current=_current.ContinueWith(prev=>action());
    }
}
UPD

更新的化学需氧量