WPF按钮文字未更改是我的用户界面被线程代码锁定

时间:2017-08-23 13:04:23

标签: c# wpf multithreading

我正在使用基本的绘图程序,我编写了一些保存代码,将当前绘制窗口导出到png文件。我改变了代码以在新线程上执行,所以我想。但是,当我运行更改我的保存按钮文本的代码时,它不会更新,直到图像保存完毕。

我的第一件事很奇怪我可以在SaveBtnClick的行中放置一个断点,将文本更改为" Saving"并且这不会影响按钮。我意识到在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
        }

1 个答案:

答案 0 :(得分:0)

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

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