使用Task.run在c#中使用非阻塞锁

时间:2018-04-16 09:35:18

标签: c# multithreading asynchronous locking task

想象一下,我知道一些对象至少会被两个线程使用。比方说,某种共享缓存。

一个线程将是一个工作线程,并将持续运行一个循环,该循环利用缓存中的信息。

其他“客户端”线程可以向缓存提交新信息。工作线程应完成其当前循环并在下一次迭代中获取新信息。

如果程序在所有客户端信息都在缓存之前结束并不重要,因为它只是一个应用程序端缓存。我们会将信息保存在其他地方,比如数据库。

在这种情况下,在我看来,一个“简单”的解决方案是共享状态和使用锁。但我不希望“客户端”线程在让客户端线程继续之前必须等待工作线程在每次迭代结束时释放其锁,这似乎是不必要的。我可能会描述客户端添加一个“锁定绑定”(而不是CPU或IO绑定)操作,它可以异步完成。

创建一个提供Add()方法的SharedCache类是否存在根本性的错误,在该方法中我们发出一个Task.Run,​​其中正在运行的代码需要锁定才能添加到底层缓存对象?据我所知,Task.Run实际上是一种并行运行操作的方式,而不是异步运行,但我不确定这里的“纯异步”解决方案是什么样的。

一方面这种方法看似简单易行,但另一方面我已经阅读了Stephen Cleary关于异步的文章,包括题为“不要在实现中使用Task.Run”的文章,这就是这个解决方案建议做。

这是使用此模式的示例控制台应用程序。我提供了两种不同的Add方法 - 一种是阻止,另一种是阻止。

运行此代码时,我注意到锁并不总是按照请求的顺序授予,但这不是问题。

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace asynclockwait {
    public class SharedCache {
        private List<char> cache;
        private readonly object locker;
        public SharedCache() { 
            locker = new object(); cache = new List<char>(); 
        }
        public void AddWithoutBlocking(char c) {
            Task.Run(() => { lock(locker) { cache.Add(c); } });
        }
        public void AddWithBlocking(char c) {
            lock(locker) { cache.Add(c); }
        }
        public void Use() {
            lock (locker) { int i = cache.Count; Thread.Sleep(200); } // do something which takes a little while
        }
        public void Dump() {
            foreach (char c in cache) { Console.Write(c); }
        }
    }

    class Program {
        private static bool stopping;
        private static SharedCache cache = new SharedCache();

        public static void loop() {
            while (!stopping) {
                cache.Use();
                Thread.Sleep(50); // create a window for people waiting on the lock to make use of it before we ask for it back
            }
        }
        static void Main(string[] args) {
            Thread t1 = new Thread(loop);
            t1.Start();

            while (!stopping) {
                ConsoleKeyInfo k = Console.ReadKey();
                if (k.Key == ConsoleKey.X) {
                    stopping = true;
                } else {
                    //cache.AddWithBlocking(k.KeyChar);
                    cache.AddWithoutBlocking(k.KeyChar);
                }
            }
            Console.WriteLine();
            cache.Dump();
            Console.Read();
        }
    }
}

1 个答案:

答案 0 :(得分:0)

我会用这种方式设计情况:

  1. 初始化SharedCache对象
  2. 初始化共享队列
  3. 创建一个其作业是等待事件的线程(AutoReset / ManualReset) 事件设置时。锁定共享队列 - 提取所有项目(.ToList())并清除队列。释放锁定。此外,它将继续逐个处理提取的项目列表。完成处理手头的所有工作后,它将返回事件并等待。
  4. 客户端呼叫将锁定共享队列,将数据排入队列,解锁然后设置事件。