C# 异步修改相关对象的列表会产生意外的结果

C# 异步修改相关对象的列表会产生意外的结果,c#,algorithm,async-await,C#,Algorithm,Async Await,我有一个对象列表,这些对象之间可以有一个或多个关系。我想浏览这个列表,并将每个对象与列表中的所有其他对象进行比较,在比较对象时设置关系。因为现实生活中的这种比较相当复杂和耗时,所以我尝试异步进行 我已经很快地整理了一些示例代码,它们以一种相当简单的方式说明了当前的问题 class Program { private static readonly Word[] _words = { new Word("Beef"),

我有一个对象列表,这些对象之间可以有一个或多个关系。我想浏览这个列表,并将每个对象与列表中的所有其他对象进行比较,在比较对象时设置关系。因为现实生活中的这种比较相当复杂和耗时,所以我尝试异步进行

我已经很快地整理了一些示例代码,它们以一种相当简单的方式说明了当前的问题

class Program
    {
        private static readonly Word[] _words =
        {
            new Word("Beef"),
            new Word("Bull"),
            new Word("Space")
        };

        static void Main()
        {
            var tasks = new List<Task>();

            foreach (var word in _words)
            {
                tasks.Add(CheckRelationShipsAsnc(word));
            }

            Task.WhenAll(tasks);
        }

        static async Task CheckRelationShipsAsnc(Word leftWord)
        {
            await Task.Run(() =>
            {
                foreach (var rightWord in _words)
                {
                    if(leftWord.Text.First() == rightWord.Text.First())
                    {
                        leftWord.RelationShips.Add(rightWord);
                    }
                }
            });
        }
    }

    class Word
    {
        public string Text { get; }
        public List<Word> RelationShips { get; } = new List<Word>();

        public Word(string text)
        {
            if(string.IsNullOrEmpty(text)) throw new ArgumentException();
            Text = text;
        }

        public override string ToString()
        {
            return $"{Text} ({RelationShips.Count} relationships)";
        }
    }
类程序
{
专用静态只读字[]\u字=
{
新词(“牛肉”),
新词(“公牛”),
新词(“空格”)
};
静态void Main()
{
var tasks=新列表();
foreach(单词中的变量)
{
添加(checkrelationshipsanc(word));
}
任务。WhenAll(任务);
}
静态异步任务检查关系SASNC(Word leftWord)
{
等待任务。运行(()=>
{
foreach(var rightWord in_words)
{
if(leftWord.Text.First()==rightWord.Text.First())
{
leftWord.RelationShips.Add(rightWord);
}
}
});
}
}
类词
{
公共字符串文本{get;}
公共列表关系{get;}=new List();
公共字(字符串文本)
{
if(string.IsNullOrEmpty(text))抛出新的ArgumentException();
文本=文本;
}
公共重写字符串ToString()
{
返回$“{Text}({RelationShips.Count}RelationShips)”;
}
}

预期的结果是“空间”没有关系,而“牛”和“牛肉”两个词之间有一种关系。我得到的是所有的话都没有关系。我很难理解到底是什么问题

您还应该使
Main
方法
async
并等待。否则,
whalll
的结果任务将不会运行其执行。您还可以使用
Linq

static async Task Main()
{
var tasks=\u words.Select(CheckRelationShipsAsync);
等待任务。何时(任务);
}
您还可以使用或方法,它同步运行并阻止当前线程(因此,不推荐使用这种方法)。但是它不需要使
Main
method
async

var tasks=\u words.Select(CheckRelationShipsAsync);
var task=task.WhenAll(任务);
task.Wait();

static void Main()
{
var tasks=\u words.Select(CheckRelationShipsAsync);
Task.WaitAll(tasks.ToArray());
}
第二点是,当你检查这些关系时,你没有跳过单词本身,而且每一个单词的结尾都与它本身有关系。您应该添加
leftWord!=rightWord
foreach
循环中设置条件以获得预期结果

预期的结果是“空间”没有关系,而 “公牛”和“牛肉”这两个词之间有一种关系


您的算法的时间复杂度为
O(n^2)
。如果您有大量项目要相互比较,则这是一个问题。例如,如果您有1000个项目,这将为您提供1000*1000=1000000(一百万)个比较

考虑使用另一种方法。我不知道这是否适用于您的实际问题,但在本例中,假设每个单词都以大写字母a..Z开头,您可以将相关单词按首字母存储在单词列表长度为26的数组中

var a = new List<Word>[26];

// Initialize array with empty lists
for (int i = 0; i < a.Length; i++) {
    a[i] = new List<Word>();
}

// Fill array with related words
foreach (var word in _words) {
    a[word.Text[0] - 'A'].Add(word); // Subtracting 'A' yields a zero-based index.
}
var a=新列表[26];
//使用空列表初始化数组
for(int i=0;i
请注意,原始解决方案有两个嵌套循环(其中一个隐藏在调用
checkrelationshipsanc
的内部)。此解决方案只有一个级别的循环,到目前为止,其时间复杂度为
O(n)

现在,您可以在一个列表中的相应数组位置找到所有相关单词。根据这些信息,您现在可以连接同一列表中的单词。这部分仍然是O(n^2)
;然而,这里的
n
要小得多,因为is仅指相关列表中的单词,而不是初始
\u单词
数组的长度

根据您的实际问题是如何表述的,最好使用
字典
代替我的数组
a
。数组解决方案需要索引。在现实世界的问题中,关系条件可能无法作为一个指数公式化。字典需要一个键,任何类型的对象都可以用作键。请参阅:


以这种方式优化的算法可能比多任务解决方案更快。

Task.WaitAll()
一个小小的迂腐修正:术语“异步”在这种情况下不合适。正确的术语是“并行”(尽管“并行”也很合适)。如果你有兴趣知道这些术语的确切含义,你可以看看。另外,在处理并行代码时,你必须时刻警惕可能出现的线程安全问题。例如,属性
Word.RelationShips
的类型是什么?如果它是一个
列表
,那么在没有同步的情况下从多个线程修改它可能很容易导致列表的内部状态损坏。