2017-08-23 11 views
0

基本的なペイントプログラムで作業していて、現在のペイントウィンドウをpngファイルにエクスポートするセーブコードを書きました。私はそのコードを新しいスレッドで実行するように変更したので、私は考えました。しかし、保存ボタンのテキストを変更するコードを実行すると、画像が保存されるまで更新されません。WPFボタンのテキストが変更されないUIがスレッドコードからロックされる

私の最初のことは奇妙です。SaveBtnClickの行にブレークポイントを置くと、テキストが「保存」に変わり、ボタンには影響しません。私はSaveAsPngAsyncで実行されているコードがまだ私のUIをロックしていることを認識しています。私はそれがschedulerのためだと思うが、そのコードブロックがなければスレッドSTAについてのエラーが出る。

コード:

private void SaveBtn_Click(object sender, RoutedEventArgs e) 
     { 
      ChangeSaveBtnText("Saving"); 
      if (pictureSaveTask == null) 
      { 
       pictureSaveTask = SaveAsPngAsync(); 
       pictureSaveTask.ContinueWith((t) => 
       { 
        pictureSaveTask = null; 
        ChangeSaveBtnText("Save"); 

       }); 
      } 
      else 
      { 
       cts.Cancel(); 
       ChangeSaveBtnText("Save"); 
      } 

     } 

     private void ChangeSaveBtnText(string text) 
     { 
      Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => { 
       SaveBtn.Content = text; 
      })); 
     } 

     /// <summary> 
     /// Saves the current state of the DrawingCanvas as Xaml so that it can re parsed on a different thread then exported as a png. 
     /// </summary> 
     /// <returns>Task of executing code</returns> 
     private async Task SaveAsPngAsync() 
     { 
      //Stores current DrawingCanvas at time of save as a string to be parsed later. 
      var canvasAtSaveState = XamlWriter.Save(DrawingCanvas); 
      var ActualWidth = DrawingCanvas.ActualWidth; 
      var ActualHeight = DrawingCanvas.ActualHeight; 

      //Not really sure why this is required but without, throws Error: Thread must be STA. 
      var scheduler = TaskScheduler.FromCurrentSynchronizationContext(); 

      //This is the short hand way of creating a task and running it instantly 
      await Task.Factory.StartNew(() => { 
       try 
       { 
        // 'as' object syntax is a short hand conversion 
        InkCanvas cc = XamlReader.Parse(canvasAtSaveState) as InkCanvas; 

        //Pretty clear that this gets the users path to their desktop 
        var desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); 

        //File stream is the easiest way to export a stream of bytes into a file 
        //Important usage of a C# syntax "using" statements are very important to prevent memory leaks especially with streams 
        using (var fs = new FileStream(System.IO.Path.Combine(desktopPath, "picture.png"), FileMode.Create, FileAccess.ReadWrite)) 
        { 
         //Must use Actual(Value) because Simply using Width or Height doesn't change the window size changes or scales 
         RenderTargetBitmap rtb = new RenderTargetBitmap((int)ActualWidth, (int)ActualHeight, 96d, 96d, PixelFormats.Default); 

         rtb.Render(cc); 

         //PngBitmapEncoder because my InkCanvas has a transparent background 
         BitmapEncoder pngEncoder = new PngBitmapEncoder(); 

         //Adds a single frame to the image because this isn't a Gif 
         pngEncoder.Frames.Add(BitmapFrame.Create(rtb)); 

         //Begins saving the pngBytes into a stream that will then store the bytes into a file 
         pngEncoder.Save(fs); 
        } 
       } 
       catch (Exception ex) 
       { 
        //Catches anything just in case something explodes 
       } 
      }, cts.Token, TaskCreationOptions.PreferFairness, scheduler); //PreferFairness tells the thread pool that this new worker thread isn't super important 
     } 
+0

は、それはです。ボタンクリックハンドラであなたの仕事を待っているわけではありません。 – dymanoid

+0

'SaveAsPngAsync'にスケジューラ、作成オプション、トークン、async/awaitという混乱する点がたくさんあります。あなたが必要とするものはありません。これらをすべて削除すると、意図したとおりに動作します。 – Sinatr

+0

@Sinatr私は、保存操作をキャンセルするためのトークンが必要だと思っています。私はスレッドのSTAに関する例外が発生するため、スケジューラを与えました。 –

答えて

0

あなたの非同期コードは次のようになります。あなたはボタンが更新され表示されていない、なぜあなたの仕事は、UIスレッドで同期実行されている

class YourClass 
{ 
    private CancellationTokenSource cts; 

    private async void SaveBtn_Click(object sender, RoutedEventArgs e) 
    { 
     // if cts isn't null, then a saving task is already running -> cancel it 
     if (cts != null) 
     { 
      cts.Cancel(); 
     } 
     else 
     { 
      // otherwise, create a new token source and await the saving task 
      // this runs on the UI thread 
      cts = new CancellationTokenSource(); 
      SaveBtn.Content = "Saving"; 
      try 
      { 
       // the task itself will probably run on a thread pool's thread, 
       // but after it finishes, this method will continue on the UI thread 
       await Task.Run(() => SaveAsPng(cts.Token), cts.Token); 
      } 
      catch (OperationCanceledException) 
      { 
       // insert your cancellation handling if needed 
       // this runs on the UI thread 
      } 
      finally 
      { 
       // This runs on the UI thread 
       SaveBtn.Content = "Save"; 
       cts.Dispose(); 
       cts = null; 
      } 
     } 
    } 

    private void SaveAsPng(CancellationToken ct) 
    { 
     // your synchronous implementation 
     // don't forget to check ct.IsCancellationRequested in this method 
     // or call ct.ThrowIfCancellationRequested() 

     // no need to start any tasks here 
     // don't use dispatchers, schedulers etc. 
     // just a plain old synchronous code 
    } 
} 
関連する問題