我有一个可以从多个线程访问的类的实例。此类接受此调用并将元组添加到数据库中。我需要以串行方式完成此操作,因为由于某些数据库约束,并行线程可能会导致数据库不一致。
由于我不熟悉C#中的并行性和并发性,我这样做了:
private BlockingCollection<Task> _tasks = new BlockingCollection<Task>();
public void AddDData(string info)
{
Task t = new Task(() => { InsertDataIntoBase(info); });
_tasks.Add(t);
}
private void InsertWorker()
{
Task.Factory.StartNew(() =>
{
while (!_tasks.IsCompleted)
{
Task t;
if (_tasks.TryTake(out t))
{
t.Start();
t.Wait();
}
}
});
}
AddDData
是由多个线程调用的InsertDataIntoBase
,.Take()
是一个非常简单的插入,应该花费几毫秒。
问题在于,由于某种原因,我的知识缺乏使我无法弄清楚,有时候任务被召唤两次!总是这样:
T1 T2 T3 T1&lt; - PK错误。 T4 ...
我是否理解private BlockingCollection<Data> _tasks = new BlockingCollection<Data>();
public void AddDData(Data info)
{
_tasks.Add(info);
}
private void InsertWorker()
{
Task.Factory.StartNew(() =>
{
while (!_tasks.IsCompleted)
{
Data info;
if (_tasks.TryTake(out info))
{
InsertIntoDB(info);
}
}
});
}
完全错误,我是否遗漏了某些内容,或者我的生产者/消费者实施真的很糟糕?
最诚挚的问候, 圣拉斐尔
更新
正如所建议的那样,我使用这种架构进行了快速沙盒测试实现,正如我怀疑的那样,它并不能保证在上一个任务完成之前不会触发任务。
所以问题仍然存在:如何正确排队任务并按顺序激活它们?
更新2:
我简化了代码:
{{1}}
请注意,由于我依赖同步的InsertIntoDB调用(因为它在循环中),我摆脱了任务,但仍然没有运气......这一代很好,我绝对相信只有唯一的实例才是去队列。但无论我尝试,有时同一个对象被使用两次。
答案 0 :(得分:1)
我认为这应该有效:
private static BlockingCollection<string> _itemsToProcess = new BlockingCollection<string>();
static void Main(string[] args)
{
InsertWorker();
GenerateItems(10, 1000);
_itemsToProcess.CompleteAdding();
}
private static void InsertWorker()
{
Task.Factory.StartNew(() =>
{
while (!_itemsToProcess.IsCompleted)
{
string t;
if (_itemsToProcess.TryTake(out t))
{
// Do whatever needs doing here
// Order should be guaranteed since BlockingCollection
// uses a ConcurrentQueue as a backing store by default.
// http://msdn.microsoft.com/en-us/library/dd287184.aspx#remarksToggle
Console.WriteLine(t);
}
}
});
}
private static void GenerateItems(int count, int maxDelayInMs)
{
Random r = new Random();
string[] items = new string[count];
for (int i = 0; i < count; i++)
{
items[i] = i.ToString();
}
// Simulate many threads adding items to the collection
items
.AsParallel()
.WithDegreeOfParallelism(4)
.WithExecutionMode(ParallelExecutionMode.ForceParallelism)
.Select((x) =>
{
Thread.Sleep(r.Next(maxDelayInMs));
_itemsToProcess.Add(x);
return x;
}).ToList();
}
这确实意味着使用者是单线程的,但允许多个生产者线程。
答案 1 :(得分:0)
来自您的评论
“我简化了此处显示的代码,因为数据不是字符串”
我假设传递给AddDData的info
参数是一个可变引用类型。确保调用者没有对多个调用使用相同的info
实例,因为该引用是在任务lambda中捕获的。
答案 2 :(得分:0)
根据您提供的跟踪,唯一合乎逻辑的可能性是您已拨打InsertWorker
两次(或更多)。因此有两个后台线程等待项目出现在集合中,偶尔它们都设法抓取一个项目并开始执行它。