C# 使用FIFO同步运行任务的好方法?

C# 使用FIFO同步运行任务的好方法?,c#,asynchronous,async-await,task,fifo,C#,Asynchronous,Async Await,Task,Fifo,目前,我在.NET中使用async/await和tasks完成了我的第一步,我非常兴奋异步运行是多么容易!然而,目前我必须通过串行端口与设备通信。由于同一时间只能有一个连接,我只编写了几个扩展方法来运行所有这些方法,这些方法来自不同的任务/线程,以先进先出的顺序同步运行: public static class Extensions { private readonly static object LockObject = new object(); public static

目前,我在.NET中使用async/await和tasks完成了我的第一步,我非常兴奋异步运行是多么容易!然而,目前我必须通过串行端口与设备通信。由于同一时间只能有一个连接,我只编写了几个扩展方法来运行所有这些方法,这些方法来自不同的任务/线程,以先进先出的顺序同步运行:

public static class Extensions
{
    private readonly static object LockObject = new object();

    public static Task<TResult> RunAfter<TResult>(this Task<TResult> task, ConcurrentQueue<Task> others)
        => (Task<TResult>)task.RunAllSynchronously(others);

    public static Task RunAfter(this Task task, ConcurrentQueue<Task> others)
        => task.RunAllSynchronously(others);

    private static Task RunAllSynchronously(this Task task, ConcurrentQueue<Task> others)
    {
        if (others == null) throw new ArgumentNullException("The value of " + nameof(others) + " is null!");
        lock (LockObject)
        {
            others.Enqueue(task);
            Task currentTask;
            while (others.TryDequeue(out currentTask))
            {
                currentTask.RunSynchronously();
                if (currentTask == task) break;
            }
        }
        return task;
    }
}
公共静态类扩展
{
私有只读静态对象LockObject=新对象();
公共静态任务RunAfter(此任务任务,ConcurrentQueue其他任务)
=>(任务)任务。同步运行(其他);
公共静态任务RunAfter(此任务任务,ConcurrentQueue其他任务)
=>任务。同步运行(其他);
私有静态任务同步运行(此任务任务,ConcurrentQueue其他任务)
{
如果(others==null)抛出新的ArgumentNullException(“nameof(others)+”的值为null!”);
锁定(锁定对象)
{
其他。排队(任务);
任务当前任务;
while(others.TryDequeue(out currentTask))
{
currentTask.RunSynchronously();
如果(currentTask==任务)中断;
}
}
返回任务;
}
}
这种方法似乎是一种好方法,还是应该区别对待这种情况?

为什么要同步运行它们

您应该异步运行任务,并使用
async
wait
逐个执行任务:

 Task currentTask;
 while (others.TryDequeue(out currentTask))
 {
      await currentTask;
      if (currentTask == task) break;
 }
另一方面,查看您的代码,我根本找不到使用
lock
(线程同步)的理由针对某些共享资源同步线程(即,某些对象可能会或可能不会被多个线程读取/修改)。您的方法可以修改为:

private static async Task RunAllAsync(this Task task, ConcurrentQueue<Task> others)
{
    // Design by contract rocks ;)
    // See:  https://msdn.microsoft.com/en-us/library/dd264808(v=vs.110).aspx
    Contracts.Requires(task != null);
    Contracts.Requires(others != null);

    others.Enqueue(task);

    // See how I've improved your loop. Since ConcurrentQueue.TryDequeue
    // will return false if other thread has called it already, your loop
    // should try to dequeue again until it returns true, and it should
    // break if dequeued task is the task against which the extension method
    // was called or the concurrent queue has no more items, to prevent a 
    // possible infinite loop
    do
    { 
       Task currentTask;
       if(others.TryDequeue(out currentTask))
          await currentTask;

    }
    while (currentTask == task || others.Count > 0);

    return task;
}
private static async Task RunAllAsync(此任务任务,ConcurrentQueue其他任务)
{
//根据合同进行设计;)
//见:https://msdn.microsoft.com/en-us/library/dd264808(v=vs.110).aspx
Contracts.Requires(任务!=null);
合同。需要(其他!=null);
其他。排队(任务);
//看看我是如何改进你的循环的。自从ConcurrentQueue.TryDequeue
//如果其他线程已经调用了它,则返回false
//应该再次尝试出列,直到返回true,并且应该
//如果出列任务是扩展方法所针对的任务,则中断
//已调用或并发队列没有更多项,以防止
//可能无限循环
做
{ 
任务当前任务;
if(others.TryDequeue(out currentTask))
等待当前任务;
}
而(currentTask==task | | others.Count>0);
返回任务;
}
更新 OP说:

我可能忘了说,ConcurrentQueue是 应在线程之间共享的资源。即。 对每个新任务调用Task.RunAllSynchronously()(访问 SerialPort),此调用可能来自不同的线程。也, 我无法确保RunAllSynchronously()在所有 当前正在运行(或排队)的任务已完成(我可以,但是 因此,我不得不在分机外使用锁之类的东西 方法,这并不是拥有一个扩展方法的好方法

这就是您使用
ConcurrentQueue
的原因。线程安全是内部管理的。如果您调用
ConcurrentQueue.TryDequeue
并有多个线程同时调用它,则只有一个线程会赢,其他线程会收到
false
作为返回值,并且不会分配
out
参数。请参阅:

ConcurrentQueue在内部处理所有同步 线程在同一时刻调用TryDequeue,两者都不是 操作被阻止。当检测到两个线程之间存在冲突时, 一个线程必须再次尝试检索下一个元素,而 同步是在内部处理的

TrydQueue尝试从队列中删除元素。如果方法为 如果成功,则删除该项并返回true; 否则,它将返回false 队列上的其他操作。如果队列中填充了代码 例如q.Enqueue(“a”);q.Enqueue(“b”);q.Enqueue(“c”);和两个 线程同时尝试将一个元素出列,一个线程将 退出a队列,另一个线程将退出b队列 TryDequeue将返回true,因为它们都能够将 元素。如果每个线程返回到另一个元素的出列, 其中一个线程将退出c队列并返回true,而另一个线程则返回true 线程将发现队列为空,并返回false

为什么要同步运行它们

您应该异步运行任务,并使用
async
wait
逐个执行任务:

 Task currentTask;
 while (others.TryDequeue(out currentTask))
 {
      await currentTask;
      if (currentTask == task) break;
 }
另一方面,查看您的代码,我根本找不到使用
lock
(线程同步)的理由。您可以针对某个共享资源同步线程(即,某个对象可能会或可能不会被多个线程读取/修改)。您可以将该方法修改为:

private static async Task RunAllAsync(this Task task, ConcurrentQueue<Task> others)
{
    // Design by contract rocks ;)
    // See:  https://msdn.microsoft.com/en-us/library/dd264808(v=vs.110).aspx
    Contracts.Requires(task != null);
    Contracts.Requires(others != null);

    others.Enqueue(task);

    // See how I've improved your loop. Since ConcurrentQueue.TryDequeue
    // will return false if other thread has called it already, your loop
    // should try to dequeue again until it returns true, and it should
    // break if dequeued task is the task against which the extension method
    // was called or the concurrent queue has no more items, to prevent a 
    // possible infinite loop
    do
    { 
       Task currentTask;
       if(others.TryDequeue(out currentTask))
          await currentTask;

    }
    while (currentTask == task || others.Count > 0);

    return task;
}
private static async Task RunAllAsync(此任务任务,ConcurrentQueue其他任务)
{
//根据合同进行设计;)
//见:https://msdn.microsoft.com/en-us/library/dd264808(v=vs.110).aspx
Contracts.Requires(任务!=null);
合同。需要(其他!=null);
其他。排队(任务);
//看看我是如何改进你的循环的。自从ConcurrentQueue.TryDequeue
//如果其他线程已经调用了它,则返回false
//应该再次尝试出列,直到返回true,并且应该
//如果出列任务是扩展方法所针对的任务,则中断
//已调用或并发队列没有更多项,以防止
//可能无限循环
return await Task.Run ( () => Write(t));
private BufferBlock<T> bufferBlock = new BufferBlock<T>();

private async Task ProduceAsync()
{
    while (objectsToProcessAvailable())
    {
        T nextObject = GetNextObjectToProcess()
        await bufferBlock.SendAsync(nextObject);
    }
    // nothing to process anymore: mark complete:
    bufferBlock.Complete();
}
private Task ConsumeAsync()
{
    // as long as there is something to process: fetch it and process it
    while (await bufferBlock.OutputAvailableAsync())
    {
        T nextToProcess = await bufferBlock.ReceiveAsync();
        // use WriteAsync to send to the serial port:
        await WriteAsync(nextToProcess);
    }
    // if here: no more data to process. Return
}
private async Task ProduceConsumeAsync()
{
    var taskProducer = ProduceAsync();
    // while the producer is busy producing, you can start the consumer:
    var taskConsumer = ConsumeAsync();
    // while both tasks are busy, you can do other things,
    // like keep the UI responsive
    // after a while you need to be sure the tasks are finished:
    await Task.WhenAll(new Task[] {taskProducer, taskConsumer});
}
private async void OnButton1_clicked(object sender, ...)
{
    await ProduceConsumeAsync()
}
private void MyFunction()
{
    // start produce consume:
    var myTask = Task.Run( () => ProduceConsumeAsync());
    // while the task is running, do other things.
    // when you need the task to finish:
    await myTask;
 }
private static ConcurrentQueue<Task> Tasks { get; } = new ConcurrentQueue<Task>();

public async static Task RunAlone(this Task task)
{
    Tasks.Enqueue(task);

    do
    {
        var nextTask = Tasks.First();

        if (nextTask == task)
        {
            nextTask.Start();
            await nextTask;
            Task deletingTask;
            Tasks.TryDequeue(out deletingTask);
            break;
        }
        else
        {
            nextTask.Wait();
        }
    } while (Tasks.Any());
}

public async static Task<TResult> RunAlone<TResult>(this Task<TResult> task)
{
    TResult result = default(TResult);
    Tasks.Enqueue(task);

    do
    {
        var nextTask = Tasks.First();

        if (nextTask == task)
        {
            nextTask.Start();
            result = await (Task<TResult>)nextTask;
            Task deletingTask;
            Tasks.TryDequeue(out deletingTask);
            break;
        }
        else
        {
            nextTask.Wait();
        }
    } while (Tasks.Any());

    return result;
}