线程安全的集合 可以防止 多个线程 以互相冲突的方式 访问集合
为了对集合进行线程安全的访问,定义了
IProducerConsumerCollection<T>接口
TryAdd():- 尝试给集合添加一项
- 如果集合禁止添加项,这个操作就可能失败
- 返回的布尔值说明是成功还是失败
TryTake():- 尝试返回集合中的项
- 如果集合禁止返回项,这个操作就可能失败
- 返回的布尔值说明是成功还是失败
并发集合
System.Collections.Concurrent中提供:
ConcurrentQueue<T>- 用一种免锁定的算法实现。使用在内部合并到一个链表中的32项数组。
- 有以下方法
Enqueue()TryDequeue()TryPeek()
- 实现了
IProducerConsumerCollection<T>TryAdd()调用Enqueue()TryTake()调用TryDequeue()
ConcurrentStack<T>- 在内部,使用其元素的链表
- 有以下方法
Push()PushRange()TryPeek()Pop()TryPopRange()
ConcurrentBag<T>- 没有定义添加或提取项的任何顺序
- 把线程映射到内部使用的数组上,因此尝试减少锁定
- 有以下方法
Add()TryPeek()TryTake()
ConcurrentDictionary<TKey, TValue>- 有以下方法,以非阻塞的方式访问成员
TryAdd()TryGetValue()TryRemove()TryUpdate()
- 有以下方法
UpdateOrAdd()- 如果键没有添加到字典中,第二个参数 就定义应该设置的值
- 如果键已经存在于字典中,
updateValueFactory参数就定义值的改变方式
- 没有实现
IProducerConsumerCollection<T>
- 有以下方法,以非阻塞的方式访问成员
BlockingCollection<T>- 在 添加 或 提取 元素 之前,会阻塞线程 并一直等待
- 有以下方法
Add()Take()TryAdd()TryTake()
ConcurrentXXX 集合 是线程安全的。
如果某个动作不适用于线程的当前状态,就返回
false。在继续之前,总是需要确认 添加 或提取元素是否成功
不能相信 集合会完成任务
BlockingCollection<T>
在 添加 或 提取 元素 之前,会阻塞线程 并一直等待
Add()添加元素- 阻塞线程,直到可以执行
- 有重载版本:允许传递一个
CancellationToken,来取消调用
Take()删除元素- 阻塞线程,直到可以执行
- 有重载版本:允许传递一个
CancellationToken,来取消调用
如果 不希望 线程无限期地等待下去,且 不希望 从外部取消调用,则可以使用以下方法:(可以设置一个超时)
TryAdd()TryTake()
此处,超时:表示在调用失败之前,阻塞线程 的最长时间
示例:
- 读取文件名
- 加载内容
- 处理内容
- 转换内容
- 添加颜色
- 显示内容
class Program
{
static void Main()
{
StartPipelineAsync().Wait();
Console.ReadLine();
}
public static async Task StartPipelineAsync()
{
var fileNames = new BlockingCollection<string>();
var lines = new BlockingCollection<string>();
var words = new ConcurrentDictionary<string, int>();
var items = new BlockingCollection<Info>();
var coloredItems = new BlockingCollection<Info>();
Task t1 = PipelineStages.ReadFilenamesAsync(@"../..", fileNames);
ColoredConsole.WriteLine("started stage 1");
Task t2 = PipelineStages.LoadContentAsync(fileNames, lines);
ColoredConsole.WriteLine("started stage 2");
Task t3 = PipelineStages.ProcessContentAsync(lines, words);
await Task.WhenAll(t1, t2, t3);
ColoredConsole.WriteLine("stages 1, 2, 3 completed");
Task t4 = PipelineStages.TransferContentAsync(words, items);
Task t5 = PipelineStages.AddColorAsync(items, coloredItems);
Task t6 = PipelineStages.ShowContentAsync(coloredItems);
ColoredConsole.WriteLine("stages 4, 5, 6 started");
await Task.WhenAll(t4, t5, t6);
ColoredConsole.WriteLine("all stages finished");
}
}
public static class PipelineStages
{
public static Task ReadFilenamesAsync(string path, BlockingCollection<string> output)
{
return Task.Factory.StartNew(() =>
{
foreach (string filename in Directory.EnumerateFiles(path, "*.cs",
SearchOption.AllDirectories))
{
output.Add(filename);
ColoredConsole.WriteLine($"stage 1: added {filename}");
}
output.CompleteAdding();
}, TaskCreationOptions.LongRunning);
}
public static async Task LoadContentAsync(BlockingCollection<string> input, BlockingCollection<string> output)
{
foreach (var filename in input.GetConsumingEnumerable())
{
using (FileStream stream = File.OpenRead(filename))
{
var reader = new StreamReader(stream);
string line = null;
while ((line = await reader.ReadLineAsync()) != null)
{
output.Add(line);
ColoredConsole.WriteLine($"stage 2: added {line}");
await Task.Delay(20);
}
}
}
output.CompleteAdding();
}
public static Task ProcessContentAsync(BlockingCollection<string> input, ConcurrentDictionary<string, int> output)
{
return Task.Factory.StartNew(() =>
{
foreach (var line in input.GetConsumingEnumerable())
{
string[] words = line.Split(' ', ';', '\t', '{', '}', '(', ')',
':', ',', '"');
foreach (var word in words.Where(w => !string.IsNullOrEmpty(w)))
{
output.AddOrUpdate(key: word, addValue: 1, updateValueFactory: (s, i) => ++i);
ColoredConsole.WriteLine($"stage 3: added {word}");
}
}
}, TaskCreationOptions.LongRunning);
}
public static Task TransferContentAsync(ConcurrentDictionary<string, int> input, BlockingCollection<Info> output)
{
return Task.Factory.StartNew(() =>
{
foreach (var word in input.Keys)
{
if (input.TryGetValue(word, out int value))
{
var info = new Info(word, value);
output.Add(info);
ColoredConsole.WriteLine($"stage 4: added {info}");
}
}
output.CompleteAdding();
}, TaskCreationOptions.LongRunning);
}
public static Task AddColorAsync(BlockingCollection<Info> input, BlockingCollection<Info> output)
{
return Task.Factory.StartNew(() =>
{
foreach (var item in input.GetConsumingEnumerable())
{
if (item.Count > 40)
{
item.Color = "Red";
}
else if (item.Count > 20)
{
item.Color = "Yellow";
}
else
{
item.Color = "Green";
}
output.Add(item);
ColoredConsole.WriteLine($"stage 5: added color {item.Color} to {item}");
}
output.CompleteAdding();
}, TaskCreationOptions.LongRunning);
}
public static Task ShowContentAsync(BlockingCollection<Info> input)
{
return Task.Factory.StartNew(() =>
{
foreach (var item in input.GetConsumingEnumerable())
{
ColoredConsole.WriteLine($"stage 6: {item}", item.Color);
}
}, TaskCreationOptions.LongRunning);
}
}
public class Info
{
public Info(string word, int count)
{
Word = word;
Count = count;
}
public string Word { get; }
public int Count { get; }
public string Color { get; set; }
public override string ToString() => $"{Count} times: {Word}";
}
public class ColoredConsole
{
private static object syncOutput = new object();
public static void WriteLine(string message)
{
lock (syncOutput)
{
Console.WriteLine(message);
}
}
public static void WriteLine(string message, string color)
{
lock (syncOutput)
{
Console.ForegroundColor = (ConsoleColor)Enum.Parse(
typeof(ConsoleColor), color);
Console.WriteLine(message);
Console.ResetColor();
}
}
}
解析
调用
CompleteAdding()通知 所有读取器 不应再等待。 因为集合中不再有新的项被添加进来了
这种情形下,调用
CompleteAdding()很重要:写入器往集合中添加项的同时,读取器从集合中读取。
public static Task ReadFilenamesAsync(string path, BlockingCollection<string> output)
{
return Task.Factory.StartNew(() =>
{
foreach (string filename in Directory.EnumerateFiles(path, "*.cs",
SearchOption.AllDirectories))
{
output.Add(filename);
ColoredConsole.WriteLine($"stage 1: added {filename}");
}
output.CompleteAdding();
}, TaskCreationOptions.LongRunning);
}
直接用input,而不适用input.
GetConsumingEnumerable()是可以的。
但是!这只会迭代当前状态的集合,而不会迭代以后添加的项
这种情形下,调用
GetConsumingEnumerable()很重要:写入器往集合中添加项的同时,读取器从集合中读取。
public static async Task LoadContentAsync(BlockingCollection<string> input, BlockingCollection<string> output)
{
foreach (var filename in input.GetConsumingEnumerable())
{
using (FileStream stream = File.OpenRead(filename))
{
var reader = new StreamReader(stream);
string line = null;
while ((line = await reader.ReadLineAsync()) != null)
{
output.Add(line);
ColoredConsole.WriteLine($"stage 2: added {line}");
await Task.Delay(20);
}
}
}
output.CompleteAdding();
}