我正在使用基本的绘图程序,我编写了一些保存代码,将当前绘制窗口导出到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
}
答案 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
}
}