【问题标题】:How to wait if event received before some action in C#?如果在 C# 中的某些操作之前收到事件,如何等待?
【发布时间】:2020-01-17 06:49:57
【问题描述】:

我有一个类,其中根据从外部应用程序接收到的事件调用方法。

public void ProcessItems(Store id,Items items)
{
    //Some logic
     UpdateValidItems(id,validItems)

}

public void UpdateValidItems(Store id,Items items)
{
        //Save in DB
}

在处理 UpdateValidItems 时,外部应用程序可能会调用“ProcessItems”。我希望如果 UpdateValidItems 正在处理并且在 UpdateValidItems 处理期间调用事件,那么它应该等到 UpdateValidItems 完成。有什么办法吗?

此外,一次可以处理多个商店。所以它应该只等待基于商店的。如果 storeId 不同,则不应等待。

【问题讨论】:

  • 使用 lock() 是一个选项吗?
  • 我们可以使用锁。
  • 检查我在下面发布的答案。它包含一个示例。

标签: c# multithreading event-handling


【解决方案1】:

我会分离传入事件和处理:

  1. 让线程等待Blocking Queue
  2. 事件写入阻塞队列
  3. 来自 0.) 的线程收到通知,将 1“行”(Id 和项目)出列
  4. 所述线程处理项目
  5. 线程再次等待,或者如果同时事件添加了更多行:处理直到队列为空,然后再次等待。

这确保:

  • 一次只有一个 Store 发生变异
  • 事件返回快
  • 同一商店的后续事件不会干扰当前处理。

您也可以查看DataFlow 以实施类似的方法。


编辑/基本示例:

public class Handler
{
    private readonly BlockingCollection<QueueEntry> _queue = new BlockingCollection<QueueEntry>();
    private readonly CancellationTokenSource _cts = new CancellationTokenSource();

    // I used a Form with a button to simulate events, so you'll have to adapt that..
    public Handler(Form1 parent)
    {
        // register for incoming Items
        parent.NewItems += Parent_NewItems;
        // Start processing on a long running Pool-Thread
        Task.Factory.StartNew(QueueWorker, TaskCreationOptions.LongRunning);
    }

    // Stop Processing
    public void Shutdown( bool doitnow )
    {
        // Mark the queue "complete" - adding is now forbidden.
        _queue.CompleteAdding();
        // If you want to stop NOW, cancel all operations
        if (doitnow ) { _cts.Cancel(); }
        // Else the Task will run until the queue has been processed.
    }

    // This is all that happens on the EDT / Main / UI Thread
    private void Parent_NewItems(object sender, NewItemsEventArgs e)
    {
        try
        {
            _queue.Add(new QueueEntry { Sender = sender, Event = e });
        }
        catch (InvalidOperationException)
        {
            // dontcare ? I didn't - You may, though.
            // Will be thrown if the queue has been marked complete.
        }
    }

    private async Task QueueWorker()
    {
        // While the queue has not been marked complete and is empty
        while (!_queue.IsCompleted)
        {
            QueueEntry entry = null;
            try
            {
                // Wait until an entry is available or until canceled.
                entry = _queue.Take(_cts.Token); 
            }
            catch ( OperationCanceledException )
            {
                // dontcare
            }
            if (entry != null)
            {
                await Process(entry, _cts.Token);
            }
        }
    }

    private async Task Process(QueueEntry entry, CancellationToken cancel)
    {
        // Dummy Processing...
        await Task.Delay(TimeSpan.FromSeconds(entry.Event.Items), cancel);
    }
}

public class QueueEntry
{
    public object Sender { get; set; }
    public NewItemsEventArgs Event { get; set; }
} 

当然,这可以调整为允许一些并发/并行处理。

【讨论】:

    【解决方案2】:
    private object lock_object = new object();
    
    public void ProcessItems(Store id,Items items)
    {
        //Some logic
    
        lock(lock_object)
        {
            UpdateValidItems(id,validItems)
        }    
    }
    

    【讨论】:

    • 我们应该在多个方法中使用这个锁吗?
    • @umer 鉴于您的要求,我认为lock 不会让您满意。
    • @Fildor 那是什么让我开心呢?
    • @umer 看我的回答 ;)
    • 锁将有助于确保方法是collade顺序的。它保持对新事件的调用,直到锁被释放。恕我直言,它完全符合要求。
    猜你喜欢
    • 2013-01-17
    • 2021-10-27
    • 1970-01-01
    • 2021-07-05
    • 1970-01-01
    • 2019-03-12
    • 2019-10-12
    • 2019-04-22
    • 1970-01-01
    相关资源
    最近更新 更多