Task<IEnumerable<T>> 线程安全问题

huangapple go评论113阅读模式
英文:

Task<IEnumerable<T>> thread safety issues

问题

我在一个任务中创建了所有变量,我没有看到任何共享状态。MyData 值之一(例如:myData.Chocolate)在我进行调用的某个实例中为 null。当我进行调用时,我没有日志,也无法复制该问题。我不确定外部调用是否返回了 null,或者是否存在任何导致该字段在特定实例中为 null 的线程问题。

您是否在以下程序中看到任何线程安全问题?

public async Task<IEnumerable<MyData>> MyMethodAsync(IEnumerable<string> data)
{
    var tasks = data.Select(async d =>
    {
        MyData myData = new MyData();

        //API call
        CandyData candyData = // await async API call to Graph Client

        myData.Chocolate = candyData.Chocolate;
        myData.Toffee = candyData.Toffee;
        // 更多类似的赋值操作

        return myData;
    });
    var results = await Task.WhenAll(tasks);
    return results;
}
英文:

I am creating all the variables within a task and I don't see any shared state. One of the MyData value (eg: myData.Chocolate) was null in one of the instances where I made a call. I didn't have logs when I made the call and I am unable to replicate it. I wasn't sure if the external call returned a null or if there was any threading issue which caused the field to be null during the specific instance.

Do you see any thread safety issues in the program below?

public async Task&lt;IEnumerable&lt;MyData&gt;&gt; MyMethodAsync(IEnumerable&lt;string&gt; data)
{
    var tasks = data.Select(async d =&gt;
    {
        MyData myData = new MyData();

        //API call
        CandyData candyData = // await async API call to Graph Client

        myData.Chocolate = candyData.Chocolate;
        myData.Toffee = candyData.Toffee;
        // more similar setters

        return myData;
    });
    var results = await Task.WhenAll(tasks);
    return results;
}

答案1

得分: 1

你的代码看起来没问题。

我用以下方式进行了测试:

async Task Main()
{
    var results = await MyMethodAsync(new [] { "A", "B", "C", });
    foreach (var result in results)
        Console.WriteLine(result.Chocolate);
}

public async Task<IEnumerable<MyData>> MyMethodAsync(IEnumerable<string> data)
{
    var tasks = data.Select(async d =>
    {
        MyData myData = new MyData();

        CandyData candyData = await GetCandyDataAsync(d);

        myData.Chocolate = candyData.Chocolate;
        myData.Toffee = candyData.Toffee;

        return myData;
    });
    var results = await Task.WhenAll(tasks);
    return results;
}

public Task<CandyData> GetCandyDataAsync(string data) =>
    Task.Run(() =>
    {
        Thread.Sleep(TimeSpan.FromSeconds(10.0));
        return new CandyData()
        {
            Chocolate = data,
            Toffee = data
        };
    });

经过10秒后,它将在控制台上产生ABC,如预期的那样。

这意味着问题可能在于你的GetCandyDataAsync的实现。

我接下来的想法是尝试清理代码以使其更易于维护和测试。

因此,我根据你的MyMethodAsync编写了这个扩展方法:

public static async Task<IEnumerable<R>> SelectMany<T, U, R>(
        this IEnumerable<T> source,
        Func<T, Task<U>> taskSelector,
        Func<T, U, R> resultSelector) =>
    await Task.WhenAll(
        source
            .Select(async t =>
            {
                U u = await taskSelector(t);
                R r = resultSelector(t, u);
                return r;
            }));

是的,这实际上是你的代码包装在一个SelectMany中,现在可以使用它来编写一个简单的LINQ查询实现你的MyMethodAsync方法。

这是实现:

public async Task<IEnumerable<MyData>> MyMethodAsync(IEnumerable<string> data) =>
    await
        from d in data
        from candyData in GetCandyDataAsync(d)
        select new MyData()
        {
            Chocolate = candyData.Chocolate,
            Toffee = candyData.Toffee,
        };

再次提到,这会产生与上面相同的结果,但将繁重的工作封装在一个便利的扩展方法中。

我在发布之前进行了完整测试。

英文:

Your code looks fine.

I tested it with the following:

async Task Main()
{
	var results = await MyMethodAsync(new [] { &quot;A&quot;, &quot;B&quot;, &quot;C&quot;, });
	foreach (var result in results)
		Console.WriteLine(result.Chocolate);
}

public async Task&lt;IEnumerable&lt;MyData&gt;&gt; MyMethodAsync(IEnumerable&lt;string&gt; data)
{
	var tasks = data.Select(async d =&gt;
	{
		MyData myData = new MyData();

		CandyData candyData = await GetCandyDataAsync(d);

		myData.Chocolate = candyData.Chocolate;
		myData.Toffee = candyData.Toffee;

		return myData;
	});
	var results = await Task.WhenAll(tasks);
	return results;
}

public Task&lt;CandyData&gt; GetCandyDataAsync(string data) =&gt;
	Task.Run(() =&gt;
	{
		Thread.Sleep(TimeSpan.FromSeconds(10.0));
		return new CandyData()
		{
			Chocolate = data,
			Toffee = data
		};
	});

That produced A, B, C to the console after 10 seconds as expected.

This means that any issue could be in your implementation of GetCandyDataAsync.

My next thought was to try to clean up the code to make it more maintainable and testable.

So, I wrote this extension method based on your MyMethodAsync:

public static async Task&lt;IEnumerable&lt;R&gt;&gt; SelectMany&lt;T, U, R&gt;(
		this IEnumerable&lt;T&gt; source,
		Func&lt;T, Task&lt;U&gt;&gt; taskSelector,
		Func&lt;T, U, R&gt; resultSelector) =&gt;
	await Task.WhenAll(
		source
			.Select(async t =&gt;
			{
				U u = await taskSelector(t);
				R r = resultSelector(t, u);
				return r;
			}));

Yes, that effectively your code wrapped in a SelectMany that can now be used to write a simple LINQ query implementation of your MyMethodAsync method.

Here it is:

public async Task&lt;IEnumerable&lt;MyData&gt;&gt; MyMethodAsync(IEnumerable&lt;string&gt; data) =&gt;
	await
		from d in data
		from candyData in GetCandyDataAsync(d)
		select new MyData()
		{
			Chocolate = candyData.Chocolate,
			Toffee = candyData.Toffee,
		};

Again, that produces the same result as above, but it's encapsulated the hard work in a handy extension method.

I fully tested before posting.

huangapple
  • 本文由 发表于 2023年8月11日 03:16:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/76878731.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定