如何同步延迟的计划任务以公平地执行它们(按顺序)?

时间:2017-05-02 15:45:12

标签: c# asynchronous windows-runtime uwp synchronization

我正在编写一种方法来安排一些需要在创建时按正确顺序执行的任务,每个任务都有固定的延迟。

基本上,我需要复制这种行为(其中 Mx 是一个调度任务的方法调用, Tx 是相应的任务):

  

M1 - M2 - M3 - M4 - M5 - M6 ...

     

------ T1 - T2 - T3 - T4 - T5 ...

这里的要点是这个方法被频繁调用(不是我,所以我无法控制)和我需要能够跳过它的执行它被两个参数调用,即#34; nullify"彼此(例如,如果最后一次调用是Foo(" a"),那么我得到Foo(" b")和Foo(" a&#) 34;),我希望能够跳过最后两个电话,因为它们无用)。这就是为什么我使用Queue而不是直接从每个方法调用中调度任务的原因。

这是我到目前为止所拥有的:

// 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);
}

现在,我有两个问题:

  1. 根据我在文档中看到的内容,TaskContinuationOptions.PreferFairness标记只会尝试来保留计划任务的原始顺序,但不保证它们会在同样的订单
  2. 我多次听说Task.Delay方法不可靠,不应该用于同步目的。因此,如果例如延迟调用实际上需要101ms而另一个99ms或98ms,则可能会使整个事情变得混乱。
  3. 由于此方法将在UI线程上执行,因此我希望避免同步阻止以避免UI问题
  4. 感谢您的帮助!

2 个答案:

答案 0 :(得分:1)

我不确定我是否理解你,但你可能想尝试异步循环(下面是一些伪代码):

将项目添加到队列:

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);
    }
}

通过循环,您的商品将永远订购。作为一个缺点,你将有一些无限工作的代码,所以你需要某种机制来在需要时暂停/恢复它。它也没有涵盖你的第三个问题,我现在无法想到以异步方式实现精确延迟的任何方法。

作为旁注:您可以保留上次安排的任务,并将新任务作为先前的任务继续进行附加。这样你也可以保留秩序。

答案 1 :(得分:0)

我认为保留一组参数要容易得多:

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);
}

此代码假定从UI线程创建了类型(并且调用了Foo