c# - 将循环计数器传递给下面基于任务的方法时出现故障的原因是什么?

标签 c# asynchronous

<分区>

  • static Task<int> GetPrimeCountAsync(int start, int stop)用于计算闭区间内的素数 [start,stop]其中 2 < start <= stop .

  • static void WrapperAsync()用于打印 GetPrimeCountAsync 的 10 次执行的列表,每个都有一个跨越 i == 0 ? 3 : 1000_000 * i 的闭区间至 1000_000 * (i + 1) - 1 , 其中i0 旋转至 9 . 这个方法里面有3种情况,只有一种被激活。

    1. case 1激活时,结果如下:

      enter image description here

    2. 当case 2被激活时,结果如下:

      enter image description here

    3. 当case 3被激活时,结果如下:

      enter image description here

问题

如果我们比较案例 1、2 和 3,在案例 1 和案例 2 中存在相同的故障,但在案例 3 中没有。案例 3 是想要的结果。

实际上,可以通过使用局部变量缓冲循环计数器来消除故障,如下所示:

int start = i == 0 ? 3 : 1000_000 * i;
int stop = 1000_000 * (i + 1) - 1;

然后只需传递 startstopGetPrimeCountAsyncWriteLine .

问题是:是什么导致了情况 1 和 2 中的这 2 个故障?

完整代码

using System;
using System.Linq;
using System.Threading.Tasks;
using static System.Console;


class Program
{
    static void Main()
    {
        WrapperAsync();
        WriteLine("Ended...");
    }


    static void WrapperAsync()
    {
        for (int i = 0; i < 10; i++)
        {
            //int start = i == 0 ? 3 : 1000_000 * i;
            //int stop = 1000_000 * (i + 1) - 1;

            // case 1: OnCompleted
            var awaiter = GetPrimeCountAsync(i == 0 ? 3 : 1000_000 * i, 1000_000 * (i + 1) - 1).GetAwaiter();
            awaiter.OnCompleted(() => WriteLine($"The number of primes between {(i == 0 ? 3 : 1000_000 * i)} and {1000_000 * (i + 1) - 1} is {awaiter.GetResult()}"));

            // case 2: ContinueWith
            //var task = GetPrimeCountAsync(i == 0 ? 3 : 1000_000 * i, (i + 1) * 1000_000 - 1);
            //task.ContinueWith(t => WriteLine($"The number of primes between {(i == 0 ? 3 : 1000_000 * i)} and {(i + 1) * 1000_000 - 1} is {t.GetAwaiter().GetResult()}"));

            // case 3: without OnCompleted and without ContinueWith
            //var task = GetPrimeCountAsync(i == 0 ? 3 : 1000_000 * i, (i + 1) * 1000_000 - 1);
            //WriteLine($"The number of primes between {(i == 0 ? 3 : 1000_000 * i)} and {(i + 1) * 1000_000 - 1} is {task.GetAwaiter().GetResult()}");
        }
    }

    //  Note that 2 < start <= stop 
    static Task<int> GetPrimeCountAsync(int start, int stop)
    {
        var count = ParallelEnumerable.Range(start, stop - start + 1)
                    .Where(i => i % 2 > 0)
                    .Count(j => Enumerable.Range(3, (int)Math.Sqrt(j) - 1).Where(k => k % 2 > 0).All(l => j % l > 0));

        return Task.Run(() => count);
    }
}

最佳答案

您描述的“故障”来自于 for 循环使用一个变量这一事实。在 awaiter.OnCompleted(() => WriteLine($"{(i == 0 ? 3 : 1000_000 * i)} 和 {1000_000 * (i + 1) - 1} 之间的质数个数为 {awaiter .GetResult()}")); 您传递给 OnCompleted 的 lambda 是 for 循环中 i 的闭包。这意味着它使用 i 的当前值,而不是您将它传递给函数时的值。当你这样做时你可以看到这种行为

List<Action> actions = new List<Action>();
for(int i = 0; i < 10; ++i)
    actions.Add(()=>Console.WriteLine(i + 1))

foreach(Action a in actions) 
    a();

它打印 10 10 次。要让它打印从 1 到 10 的数字,你必须这样做

List<Action> actions = new List<Action>();
for(int i = 0; i < 10; ++i)
{
    int temp = i;
    actions.Add(()=>Console.WriteLine(temp + 1))
} 

foreach(Action a in actions) 
    a();

This Stack Overflow question 更详细地讨论闭包。

这就是为什么如果你使用

int start = i == 0 ? 3 : 1000_000 * i;
int stop = 1000_000 * (i + 1) - 1;

你得到你想要的。没有它,任务比循环的一次迭代花费更多的时间,所以它使用 i 的下一个值。


实际上还有一些事情您应该采取不同的做法。异步方法应使用 async 关键字,因此您的签名应如下所示

static async Task<int> GetPrimeCountAsync(int start, int stop)

static async Task WrapperAsync() //notice the Task return type

您不应使用 GetAwaiter,而应使用 await。你可以阅读它here . 所以不是

return Task.Run(() => count);

你现在可以写了

return count;

把更难的东西留给编译器。

如果您想异步完成这一切,您应该只使用第二个选项并稍作更改:

var task = GetPrimeCountAsync(start, stop)
    .ContinueWith(t => WriteLine($"The number of primes between {start} and {stop} is {t.Result}"));

如果你想让它们按顺序执行你可以这样写

await GetPrimeCountAsync(start, stop)
    .ContinueWith(t => WriteLine($"The number of primes between {start} and {stop} is {t.Result}"));

而是在循环中。除此之外,如果你想在 Ended... 之前打印所有内容,你可以这样写

await WrapperAsync(); //in c# 7
//or
WrapperAsync().Wait();

Main 中。实际上,我认为您必须这样做,因为应用程序可以在到达 Main 的末尾时终止,而无需等待所有任务完成。如果您不需要在完成所有可以完成的任务之后编写 Ended...

var wrapperTask = WrapperAsync();
WriteLine("Ended...");
await wrapperTask;
//or wrapperTask.Wait();

完整代码应该大致如下所示

using System;
using System.Linq;
using System.Threading.Tasks;
using static System.Console;


class Program
{
    //since c# 7
    static async Task Main()
    {
        //use this or other options I provided
        await WrapperAsync();
        WriteLine("Ended...");
    }

    //earlier versions of c#
    static void Main() 
    {
        WrapperAsync().Wait();
        WriteLine("Ended...");
    }


    static async Task WrapperAsync()
    {
        for (int i = 0; i < 10; i++)
        {
            int start = i == 0 ? 3 : 1000_000 * i;
            int stop = 1000_000 * (i + 1) - 1;

            //you can also change it here to do exactly what you want
            await GetPrimeCountAsync(start, stop)
                .ContinueWith(t => WriteLine($"The number of primes between {start} and {stop} is {t.Result}"));
        }
    }

    //  Note that 2 < start <= stop 
    static async Task<int> GetPrimeCountAsync(int start, int stop)
    {
        var count = ParallelEnumerable.Range(start, stop - start + 1)
                    .Where(i => i % 2 > 0)
                    .Count(j => Enumerable.Range(3, (int)Math.Sqrt(j) - 1).Where(k => k % 2 > 0).All(l => j % l > 0));

        return count;
    }
}

关于c# - 将循环计数器传递给下面基于任务的方法时出现故障的原因是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45297433/

相关文章:

c# - 将代码放在等待的任务末尾与将代码放在等待 : 之后的区别

javascript - 如何从javascript获取C#代码

c# - 绘制可滚动 datagridview 时出现问题

java - 使用 CompletableFuture 从主线程退出

javascript - Async.js 设计模式

c# - 我在使用 StreamReader/StreamWriter 读取多条消息时遇到问题

c# - 使用 PostSharp Toolkit 构建在 4.5.1 上失败但适用于 4.5

c# - 在C#中使用套接字编程接收消息不起作用

javascript - 如何使用新数组更新 Promise.all?

javascript - 在回调中更新数组