我有一个Windows服务,它使用带有回调的System.Threading.Timer来更新端点,如下所示:
UpdateEndpointTimer = new Timer(
new TimerCallback(UpdateSBEndpoints),
Endpoint.Statuses.Online,
EndpointUpdateFrequency,
EndpointUpdateFrequency);
以下是我的更新方法大致如下:
private void UpdateSBEndpoints(object state)
{
...
using (var context = new TestHarnessContext())
{
var endpoints = context.Endpoints.Where(p =>
p.Binding == Endpoint.Bindings.ServiceBus
&& p.State == Endpoint.States.Enabled
&& p.Status != status).ToList();
foreach (var endpoint in endpoints)
{
//Do stuff here
}
...
}
现在,当计时器使用ThreadPool中的线程来触发回调时,我需要采取措施来控制线程。当多个线程可以在第一个线程完成工作之前从db中获取相同的端点时,会发生一个特定的问题,这会导致在foreach循环中完成重复的工作。
我知道这个问题有两个可能的解决方案,我想知道哪一个更好,更可取。解决方案为ConcurrentDictionary
和ManualResetEvent
。
在第一种情况下,我会把它放在我的foreach循环中,以确保一次只有一个线程可以在给定的端点上运行:
if (EndpointsInAction.TryAdd(endpoint.Id, endpoint.Id) == false)
// If we get here, another thread has started work with this endpoint.
return;
...
//Do stuff with the endpoint, once done, remove its Id from the dictionary
...
int id;
EndpointsInAction.TryRemove(endpoint.Id, out id);
在第二种情况下,我会像这样控制线程:
protected ManualResetEvent PubIsBeingCreated { get; set; }
protected ManualResetEvent SubIsBeingCreated { get; set; }
...
this.PubIsBeingCreated = new ManualResetEvent(true);
this.SubIsBeingCreated = new ManualResetEvent(true);
...
foreach (var endpoint in endpoints)
{
if (!this.PubIsBeingCreated.WaitOne(0))
// If we get here, another thread has started work with this endpoint.
return;
try
{
// block other threads (Timer Events)
PubIsBeingCreated.Reset();
// Do stuff
}
...
finally
{
// Restore access for other threads
PubIsBeingCreated.Set();
}
}
现在两种方法似乎都有效我想知道哪种方法更适合使用(效率更高?)。我倾向于使用ConcurrentDictionary
因为它允许更精细地过滤线程,即不允许两个线程与特定端点一起工作,而不允许两个线程与特定端点一起工作 type (ManualResetEvents中的pubs和subs)。可能有另一个优于我的解决方案,因此任何信息都将受到赞赏。
答案 0 :(得分:0)
在任何情况下,您都不应将ManualResetEvent
用于此目的。使用提供更高级别抽象的对象总是更好,但即使您想要在较低级别控制它,使用.NET的Monitor类(通过lock
语句,Monitor.Wait()
和{ {1}}方法比使用Monitor.Pulse()
更好。
您似乎有一个合理标准的生产者/消费者场景。在这种情况下,在我看来,与ManualResetEvent
相比,ConcurrentDictionary
会更好。你的生产者会把东西排队,而你的消费线程会将它们排队等待处理。
答案 1 :(得分:0)
我会这样做;我使用ConcurrentBag并让一个线程从DB加载它,多个线程通过TryTake监听它。加载行李的单个线程将处理您可能从DB中获取相同项目的问题。下面将在控制台应用程序中说明它:
class Program
{
private static ConcurrentBag<int> _bag;
public static void Main()
{
// Construct and populate the ConcurrentBag
_bag = new ConcurrentBag<int>();
var bagPopulateTask = new Task(PopulateBag);
bagPopulateTask.Start();
var bagEmptyTask1 = new Task(EmptyBag);
var bagEmptyTask2 = new Task(EmptyBag);
var bagEmptyTask3 = new Task(EmptyBag);
bagEmptyTask1.Start();
bagEmptyTask2.Start();
bagEmptyTask3.Start();
Console.ReadKey();
}
private static void EmptyBag()
{
int i;
while (true)
{
if (_bag.TryTake(out i))
{
Console.WriteLine("Thread {0} took {1}", Thread.CurrentThread.ManagedThreadId, i);
}
else
{
Console.WriteLine("Thread {0} took nothing", Thread.CurrentThread.ManagedThreadId);
}
Thread.Sleep(50);
}
}
private static void PopulateBag()
{
var i = 0;
while (i < 1000)
{
Console.WriteLine("Thread {0} added {1}", Thread.CurrentThread.ManagedThreadId, i);
_bag.Add(i);
i++;
Thread.Sleep(i);
}
}
}
请注意,如果订单很重要,您可以使用ConcurrentQueue或ConcurrentStack执行类似操作。