取消作業
到目前爲止顯示的兩個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方法在已經請求取消甚至開始之前跳過大量工作。
另請注意,該代碼現在跳過大型心形指針。 註釋引用一個網頁,該網頁會在您想要檢查數學的情況下派生公式。