代码之家  ›  专栏  ›  技术社区  ›  Bailey Miller

WPF按钮文本未更改是我的UI被线程代码锁定

  •  0
  • Bailey Miller  · 技术社区  · 8 年前

    我的第一件奇怪的事是,我可以在SaveBtnClick中的行上放置一个断点,将文本更改为“Saving”,这不会影响按钮。我意识到SaveAsPngAsync中执行的代码仍在锁定我的用户界面。我认为这是因为 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
            }
    
    1 回复  |  直到 8 年前
        1
  •  0
  •   dymanoid    8 年前

    您的异步代码应该如下所示:

    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
        }
    }