修改由Linq,await和Parallel.ForEach线程安全组合的SynchronizedCollection吗?

时间:2016-01-01 18:31:37

标签: c# multithreading async-await

嗯,标题是自我解释的。 我的代码是这样的:

var clients = new ConcurrentBag<SharingClient>();
var peers = new ConcurrentDictionary<Task, SharingClient>();
clients.Add(...); // This may happen on another thread
clients.Add(...); // This may happen on another thread

var token = cts.Token;
while (!token.IsCancellationRequested)
{
    if (clients.Count == 0)
    {
        // Waiting for more clients
        await TaskEx.Delay(500, token); // Using BCL.Async as I need to support Windows XP (.Net 4)
    }
    else if (clients.Count == 1)
    { 
        // Close the left-alone client
        clients.FirstOrDefault()?.Close(); // Close may remove or modify **_clients**.
    }
    else if (clients.Any(c => c.DataAvailable))
    {
        // There is some data, let see if we have timed out peers from last time
        foreach (var p in peers.Where(peer => !peer.Key.IsCompleted))
        {
            // Close the timed out clients, will also terminate the task
            p.Value.Close(); // Close may remove or modify **_clients**.
        }
        peers.Clear();
        // Checking for possible operations and filling the array
        Parallel.ForEach(clients.Where(c => c.DataAvailable), (item) =>
        {
            Parallel.ForEach(clients.Where(c => !c.Equals(item)), (item2) =>
            {
                peers.TryAdd(item2.Broadcast(item.Data), item2); //Broadcast may add, remove or modify **_clients**.
            });
        });
        // Minimum of 10 secs for operations to run before getting timedout
        await TaskEx.WhenAny(TaskEx.WhenAll(peers.Keys.ToArray()), TaskEx.Delay(10000, token)); // Using BCL.Async as I need to support Windows XP (.Net 4)
        // Even tho some of them may have timed out by now, as we may have no data for the next operation, we will wait for data before deciding to close them
    }
    else
    {
        // Waiting for some data to appear - Recheck as soon as OS allows us
        await TaskEx.Delay(1, token); // Using BCL.Async as I need to support Windows XP (.Net 4)
    }
}

您可以清楚地看到,我使用了ConcurrentBagConcurrentDictionary以及await,Linq,简单foreachParallel.ForEach和BCL.Async方法。

这是我第一次使用ParallelConcurrentBagConcurrentDictionary;我想让人们更熟悉框架的这些部分,告诉我逻辑,线程安全和/或新的做事方式是否存在问题。

我很满意,提前谢谢你

修改

根据Ivan的回答中提到的一个MSDN文档,为了更加安全,我应该删除.Where()方法并将其作为Parallel.ForEach合并到if的正文中。

但是,关于.FirstOrDefault().Any()方法以及它们的线程安全情况仍然很少。我知道每次访问变量时都使用lock这是一个很好的做法。但在这种情况下,因为我使用ConcurrentBag,我想确保在运行这行代码之前复制变量的必要性:

if (clients.Any(client => client.IsConnected))
{
    clients.FirstOrDefault()?.Close(); // Close may remove or modify **_clients**.

编辑2

通过反编译.FirstOrDefault().Any()方法,我发现它们都使用简单的foreach。所以我相信它们应该是线程安全的。所以现在的问题是:

在其他Parallel.ForEach内使用相同的来源运行Parallel.ForEach,然后修改此来源,好吗?或者我应该在这里改变逻辑吗?

Parallel.ForEach(clients, (item) =>
{
    if (item.DataAvailable)
    {
        Parallel.ForEach(clients, (item2) =>
        {
            if (!item2.Equals(item))
            {
                // Modify **clients**

1 个答案:

答案 0 :(得分:1)

来自ConcurrentBag Class文档:

  

线程安全

     

ConcurrentBag&lt; T&gt;的所有公共成员和受保护成员是线程安全的,可以从多个线程同时使用。 但是,通过其中一个接口访问的成员ConcurrentBag&lt; T&gt;实现(包括扩展方法)不保证是线程安全的,可能需要由调用者同步。

请注意粗体斜体段落。由于LINQ基于其中一个实现的接口(IEnumerable<T>),因此您的使用不是线程安全的。