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
, 其中i
从 0
旋转至 9
.
这个方法里面有3种情况,只有一种被激活。
case 1激活时,结果如下:
当case 2被激活时,结果如下:
当case 3被激活时,结果如下:
问题
如果我们比较案例 1、2 和 3,在案例 1 和案例 2 中存在相同的故障,但在案例 3 中没有。案例 3 是想要的结果。
实际上,可以通过使用局部变量缓冲循环计数器来消除故障,如下所示:
int start = i == 0 ? 3 : 1000_000 * i;
int stop = 1000_000 * (i + 1) - 1;
然后只需传递 start
和 stop
至 GetPrimeCountAsync
和 WriteLine
.
问题是:是什么导致了情况 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;
}
}