取消作业
到目前为止显示的两个Mandelbrot程序仅用于生成单个图像,因此一旦启动它就不可能取消该作业。但是,在一般情况下,您需要为用户提供一种便利,以摆脱冗长的后台作业。
尽管您可以将自己的一个取消系统放在一起,但System.Threading命名空间已经为您提供了一个名为CancellationTokenSource的类和一个名为CancellationToken的结构。
以下是它的工作原理:
程序创建一个CancellationTokenSource以用于特定的异步方法。 CancellationTokenSource类定义名为Token的属性,该属性返回CancellationToken。此CancellationToken值将传递给异步方法。异步方法定期调用CancellationToken的IsCancellationRequested方法。此方法通常返回false。
当程序想要取消异步操作时(可能是响应某些用户输入),它调用CancellationTokenSource的Cancel方法。下次异步方法调用CancellationToken的IsCancellationRequested方法时,该方法返回true,因为已请求取消。异步方法可以选择如何
停止运行,也许是一个简单的return语句。
然而,通常采用不同的方法。异步方法可以简单地调用ThrowIfCancellationRequested方法,而不是调用CancellationToken的IsCancellationRequested方法。如果已请求取消,则异步方法将通过引发OperationCanceledException停止执行。
这意味着await运算符必须是try块的一部分,但正如您所见,这通常是处理文件时的情况,因此它不会添加太多额外的代码,并且程序可以简单地处理取消另一种形式的例外。
MandelbrotCancellation程序演示了这种技术。 XAML文件现在有第二个按钮,标记为“取消”,最初被禁用:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MandelbrotCancellation.MandelbrotCancellationPage">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness"
iOS="0, 20, 0, 0" />
</ContentPage.Padding>
<StackLayout>
<Grid VerticalOptions="FillAndExpand">
<ContentView Padding="10, 0"
VerticalOptions="Center">
<ProgressBar x:Name="progressBar" />
</ContentView>
<Image x:Name="image" />
</Grid>
<Grid>
<Button x:Name="calculateButton"
Grid.Column="0"
Text="Calculate"
FontSize="Large"
HorizontalOptions="Center"
Clicked="OnCalculateButtonClicked" />
<Button x:Name="cancelButton"
Grid.Column="1"
Text="Cancel"
FontSize="Large"
IsEnabled="False"
HorizontalOptions="Center"
Clicked="OnCancelButtonClicked" />
</Grid>
</StackLayout>
</ContentPage>
代码隐藏文件现在有一个更广泛的OnCalculateButtonClicked方法。 首先禁用“计算”按钮并启用“取消”按钮。 它创建一个新的Cancellation TokenSource对象,并将Token属性传递给CalculateMandelbrotAsync。 OnCancelButtonClicked方法负责在CancellationTokenSource对象上调用Cancel。 CalculateMandelbrotAsync方法以与报告进度相同的速率调用ThrowIfCancellationRequested方法。 OnCalculateButtonClicked方法捕获异常,该方法通过重新启用“计算”按钮进行另一次尝试来响应:
public partial class MandelbrotCancellationPage : ContentPage
{
static readonly Complex center = new Complex(-0.75, 0);
static readonly Size size = new Size(2.5, 2.5);
const int pixelWidth = 1000;
const int pixelHeight = 1000;
const int iterations = 100;
Progress<double> progressReporter;
CancellationTokenSource cancelTokenSource;
public MandelbrotCancellationPage()
{
InitializeComponent();
progressReporter = new Progress<double>((double value) =>
{
progressBar.Progress = value;
});
}
async void OnCalculateButtonClicked(object sender, EventArgs args)
{
// Configure the UI for a background process.
calculateButton.IsEnabled = false;
cancelButton.IsEnabled = true;
cancelTokenSource = new CancellationTokenSource();
try
{
// Render the Mandelbrot set on a bitmap.
BmpMaker bmpMaker = await CalculateMandelbrotAsync(progressReporter,
cancelTokenSource.Token);
image.Source = bmpMaker.Generate();
}
catch (OperationCanceledException)
{
calculateButton.IsEnabled = true;
progressBar.Progress = 0;
}
catch (Exception)
{
// Shouldn't occur in this case.
}
cancelButton.IsEnabled = false;
}
void OnCancelButtonClicked(object sender, EventArgs args)
{
cancelTokenSource.Cancel();
}
Task<BmpMaker> CalculateMandelbrotAsync(IProgress<double> progress,
CancellationToken cancelToken)
{
return Task.Run<BmpMaker>(() =>
{
BmpMaker bmpMaker = new BmpMaker(pixelWidth, pixelHeight);
for (int row = 0; row < pixelHeight; row++)
{
double y = center.Imaginary - size.Height / 2 + row * size.Height / pixelHeight;
// Report the progress.
progress.Report((double)row / pixelHeight);
// Possibly cancel.
cancelToken.ThrowIfCancellationRequested();
for (int col = 0; col < pixelWidth; col++)
{
double x = center.Real - size.Width / 2 + col * size.Width / pixelWidth;
Complex c = new Complex(x, y);
Complex z = 0;
int iteration = 0;
bool isMandelbrotSet = false;
if ((c - new Complex(-1, 0)).MagnitudeSquared < 1.0 / 16)
{
isMandelbrotSet = true;
}
// http://www.reenigne.org/blog/algorithm-for-mandelbrot-cardioid/
else if (c.MagnitudeSquared * (8 * c.MagnitudeSquared - 3) <
3.0 / 32 - c.Real)
{
isMandelbrotSet = true;
}
else
{
do
{
z = z * z + c;
iteration++;
}
while (iteration < iterations && z.MagnitudeSquared < 4);
isMandelbrotSet = iteration == iterations;
}
bmpMaker.SetPixel(row, col, isMandelbrotSet ? Color.Black : Color.White);
}
}
return bmpMaker;
}, cancelToken);
}
}
CancellationToken也作为第二个参数传递给Task.Run。 这不是必需的,但它允许Task.Run方法在已经请求取消甚至开始之前跳过大量工作。
另请注意,该代码现在跳过大型心形指针。 注释引用一个网页,该网页会在您想要检查数学的情况下派生公式。