想象一下,我知道一些对象至少会被两个线程使用。比方说,某种共享缓存。
一个线程将是一个工作线程,并将持续运行一个循环,该循环利用缓存中的信息。
其他“客户端”线程可以向缓存提交新信息。工作线程应完成其当前循环并在下一次迭代中获取新信息。
如果程序在所有客户端信息都在缓存之前结束并不重要,因为它只是一个应用程序端缓存。我们会将信息保存在其他地方,比如数据库。
在这种情况下,在我看来,一个“简单”的解决方案是共享状态和使用锁。但我不希望“客户端”线程在让客户端线程继续之前必须等待工作线程在每次迭代结束时释放其锁,这似乎是不必要的。我可能会描述客户端添加一个“锁定绑定”(而不是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();
}
}
}
答案 0 :(得分:0)
我会用这种方式设计情况: