C# 如何同步延迟的计划任务以公平地(按顺序)执行它们?
我正在编写一个方法来计划一些任务,这些任务需要在创建时以正确的顺序执行,每个任务都有固定的延迟 基本上,我需要复制这个行为(其中,Mx是一个调度任务的方法调用,Tx是它对应的任务): M1--M2--M3--M4--M5--M6 ------T1--T2--T3--T4--T5 这里的要点是,这个方法被频繁调用(不是由我调用的,所以我无法控制),如果使用两个相互“无效”的参数调用它,我需要能够跳过它的执行(例如,如果最后一个调用是Foo(“a”),那么我得到Foo(“b”)和Foo(“a”),我希望能够跳过最后两个电话,因为它们没有用)。这就是为什么我使用C# 如何同步延迟的计划任务以公平地(按顺序)执行它们?,c#,asynchronous,windows-runtime,uwp,synchronization,C#,Asynchronous,Windows Runtime,Uwp,Synchronization,我正在编写一个方法来计划一些任务,这些任务需要在创建时以正确的顺序执行,每个任务都有固定的延迟 基本上,我需要复制这个行为(其中,Mx是一个调度任务的方法调用,Tx是它对应的任务): M1--M2--M3--M4--M5--M6 ------T1--T2--T3--T4--T5 这里的要点是,这个方法被频繁调用(不是由我调用的,所以我无法控制),如果使用两个相互“无效”的参数调用它,我需要能够跳过它的执行(例如,如果最后一个调用是Foo(“a”),那么我得到Foo(“b”)和Foo(“a”),我
队列
,而不是直接从每个方法调用内部调度任务
以下是我目前掌握的情况:
// Semaphore to synchronize the queue access
private readonly SemaphoreSlim QueueSemaphore = new SemaphoreSlim(1);
// Arguments queue
private readonly Queue<String> History = new Queue<String>();
// The last argument (needed to understand the useless calls)
private String _LastArgument;
protected void Foo(String arg)
{
// Wait and add the argument to the queue
QueueSemaphore.WaitAsync().ContinueWith(ts =>
{
History.Enqueue(arg);
QueueSemaphore.Release();
// Small delay (100ms are enough in this case)
Task.Delay(100).ContinueWith(td =>
{
// Wait again before accessing the queue
QueueSemaphore.WaitAsync().ContinueWith(tf =>
{
// Edge case for the removed calls
if (History.Count == 0)
{
QueueSemaphore.Release();
return;
}
// Get the next argument and test it
String next = History.Dequeue();
if (_LastArgument != null &&
History.Count > 0 &&
History.Peek().Equals(_LastArgument))
{
// Useless calls detected, skip them
StatesHistory.Dequeue();
QueueSemaphore.Release();
return;
}
_LastArgument= next;
SomeOtherMethodWithTheActualCode(next);
QueueSemaphore.Release();
}, TaskContinuationOptions.PreferFairness);
}, TaskContinuationOptions.PreferFairness);
}, TaskContinuationOptions.PreferFairness);
}
//同步队列访问的信号量
私有只读信号量lim QueueSemaphore=新信号量lim(1);
//参数队列
私有只读队列历史记录=新队列();
//最后一个参数(需要理解无用的调用)
私有字符串_LastArgument;
受保护的void Foo(字符串参数)
{
//等待并将参数添加到队列中
QueueSemaphore.WaitAsync().ContinueWith(ts=>
{
历史。排队(arg);
QueueSemaphore.Release();
//小延迟(在这种情况下100毫秒就足够了)
任务。延迟(100)。继续(td=>
{
//再次等待,然后再访问队列
QueueSemaphore.WaitAsync().ContinueWith(tf=>
{
//删除呼叫的边缘案例
如果(History.Count==0)
{
QueueSemaphore.Release();
返回;
}
//获取下一个参数并测试它
String next=History.Dequeue();
如果(_LastArgument!=null&&
历史记录.计数>0&&
History.Peek().Equals(_LastArgument))
{
//检测到无用的呼叫,跳过它们
StatesHistory.Dequeue();
QueueSemaphore.Release();
返回;
}
_LastArgument=next;
使用实际代码的其他方法(下一步);
QueueSemaphore.Release();
},TaskContinuationOptions.preferfairity);
},TaskContinuationOptions.preferfairity);
},TaskContinuationOptions.preferfairity);
}
现在,我有两个问题:
TaskContinuationOptions.preferfairity
标志只会尝试保持计划任务的原始顺序,但不能保证它们将以相同的顺序执行Task.Delay
方法不可靠,不应将其用于同步目的。因此,例如,如果一个延迟呼叫实际需要101毫秒,另一个需要99毫秒或98毫秒,那么可能会把整个事情搞砸谢谢你的帮助 我不确定我是否理解您,但您可能想尝试异步循环(下面是一些伪代码): 将项目添加到队列:
async Task AddToQueueAsync(WorkItem item)
{
await LockQueueAsync();
queue.Add(item);
UnlockQueue();
}
在无限循环中获取项目:
async Task InfiniteLoopThatExecutesTasksOneByOne()
{
while(true)
{
WorkItem item = null;
await LockQueueAsync();
item = InspectTheQueueAndSkipSomethingIfNeeded();
UnlockQueue();
if(item!=null)
await DispatchItemToUIThread(item);
await Task.Delay(delay);
}
}
通过循环,您的物品将始终被订购。作为一个缺点,您将有一些无限工作的代码,因此您需要某种机制在需要时暂停/恢复它。此外,它没有涵盖您的第三个问题,我目前无法想出任何异步实现精确延迟的方法
作为旁注:您可以保留计划的最后一项任务,并将新任务附加为以前的任务的继续。这样,您还可以保持秩序。我认为只保留一组参数会容易得多:
public Task Execution { get; private set; } = StartAsync();
private List<string> _requests = new List<string>();
private string _currentRequest;
private async Task StartAsync()
{
while (true)
{
if (_requests.Count != 0)
{
_currentRequest = _requests[0];
_request.RemoveAt(0);
SomeOtherMethodWithTheActualCode(_currentRequest); // TODO: error handling
_currentRequest = null;
}
await Task.Delay(100);
}
}
protected void Foo(String arg)
{
var index = _requests.IndexOf(arg);
if (index != -1)
_requests.RemoveRange(index, _requests.Count - index);
else if (arg == _currentRequest)
_requests.Clear();
_requests.Add(arg);
}
公共任务执行{get;private set;}=StartAsync();
私有列表_请求=新列表();
私有字符串_currentRequest;
专用异步任务StartAsync()
{
while(true)
{
如果(_requests.Count!=0)
{
_currentRequest=_请求[0];
_请求移除(0);
SomeOtherMethodWithTheActualCode(_currentRequest);//TODO:错误处理
_currentRequest=null;
}
等待任务。延迟(100);
}
}
受保护的void Foo(字符串参数)
{
var index=_requests.IndexOf(arg);
如果(索引!=-1)
_requests.RemoveRange(索引,_requests.Count-index);
else if(arg==\u currentRequest)
_请求。清除();
_请求。添加(arg);
}
此代码假定类型是从UI线程创建的(并且调用了
Foo
。我不需要“精确”延迟(可能是100、110,这无关紧要),但我不希望“不精确”延迟导致其继续在之前创建的继续之前安排(因为这会打乱任务顺序)。另外,我不希望在后台有一个连续循环,我需要在创建该任务的方法调用之后的固定延迟后执行每个计划任务(这样我就可以对连续调用执行该检查)。谢谢你的帮助:)@Sergio0694这两点相互矛盾。您只能得到两种方法中的一种:在创建方法后经过固定的延迟执行该方法或保留顺序。例如,假设计划的第N个方法需要很长时间才能完成。您可以等待它完成(从而保留顺序并违反时间限制),也可以启动下一个方法(从而花费一些固定时间但破坏顺序)。或者我完全错过了一些东西。@Sergio0694我在后面的回答中添加了这个,您也可以使用ContinueWith创建延续链