第二十章:異步和文件I/O.(十九)

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章