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