WPF 之上的 C# async/await Task<BitmaptImage> 问题
C# async/await Task<BitmaptImage> problem on top of WPF
微型 WPF 应用程序以 async/await 方式使用灰度输入参数转换图像 (BitmapImage)。
我阅读了很多实现,但没能成功:/
按键方式:
private async void btnConvertImage_ClickAsync(object sender, RoutedEventArgs e)
{
try
{
var cts = new CancellationTokenSource();
BitmapImage result = await ImageProcessing.GreyscaleAsync(orginalImage, cts.Token).ConfigureAwait(false);
imgPhotoConverted.Source = result;
}
}
灰度任务定义:
public static async Task<BitmapImage> GreyscaleAsync(BitmapImage inputBitmapImage, CancellationToken cancellationToken)
{
return await Task.Run(() =>
{
Bitmap inputBitmap = ToBitmap(inputBitmapImage);
Bitmap outputImage = new Bitmap(inputBitmap.Width, inputBitmap.Height);
for (int i = 0; i < inputBitmap.Width; i++)
{
for (int x = 0; x < inputBitmap.Height; x++)
{
cancellationToken.ThrowIfCancellationRequested();
Color imageColor = inputBitmap.GetPixel(i, x);
int grayScale = (int)((imageColor.R * 0.21) + (imageColor.G * 0.72) + (imageColor.B * 0.07));
Color newColor = Color.FromArgb(imageColor.A, grayScale, grayScale, grayScale);
outputImage.SetPixel(i, x, newColor);
}
}
return ToBitmapImage(outputImage);
}, cancellationToken);
}
在线:
imgPhotoConverted.Source = result;
抛出错误:
System.InvalidOperationException: 'The calling thread cannot access this object because a different thread owns it.'
我设法解决了:
- 我不必使用 BitmapSource,但我必须 Freeze() 输出 BitmaImage 并将 result 包装在适当的上下文中使用:
ThreadPool.QueueUserWorkItem(async delegate
{
// ThreadPool
var cts = new CancellationTokenSource();
BitmapImage result = await ImageProcessing.GreyscaleAsync(orginalImage, cts.Token);
result.Freeze();
sc.Post(delegate
{
// original context (UI)
imgPhotoConverted.Source = result;
cts.Cancel();
}, null);
}
我希望这对其他人有用。
谢谢!
您应该在 Stephen Clearys Blog 阅读更多关于 async/await 的信息。
按照那里的建议,您将找到一个非常聪明的解决方案
private async void btnConvertImage_ClickAsync(object sender, RoutedEventArgs e)
{
var originalImage = ( imgPhotoOriginal.Source as BitmapImage );
BitmapImage result = await Task.Run( () => originalImage.ToBitmap().ToGrayscale().ToBitmapImage() );
imgPhotoConverted.Source = result;
}
正在使用此扩展 class
public static class BitmapExtensions
{
public static Bitmap ToGrayscale( this Bitmap source, CancellationToken cancellationToken = default )
{
Bitmap output = new Bitmap( source.Width, source.Height );
for ( int i = 0; i < source.Width; i++ )
{
for ( int x = 0; x < source.Height; x++ )
{
cancellationToken.ThrowIfCancellationRequested();
var imageColor = source.GetPixel( i, x );
int grayScale = (int)( ( imageColor.R * 0.21 ) + ( imageColor.G * 0.72 ) + ( imageColor.B * 0.07 ) );
var newColor = System.Drawing.Color.FromArgb( imageColor.A, grayScale, grayScale, grayScale );
output.SetPixel( i, x, newColor );
}
}
return output;
}
public static Bitmap ToBitmap( this BitmapImage source )
{
using ( MemoryStream outStream = new MemoryStream() )
{
BitmapEncoder enc = new BmpBitmapEncoder();
enc.Frames.Add( BitmapFrame.Create( source ) );
enc.Save( outStream );
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap( outStream );
return new Bitmap( bitmap );
}
}
public static BitmapImage ToBitmapImage( this Bitmap source )
{
using ( var memory = new MemoryStream() )
{
source.Save( memory, ImageFormat.Png );
memory.Position = 0;
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = memory;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
bitmapImage.Freeze();
return bitmapImage;
}
}
}
我认为有一种更简单的方法可以做到这一点:在返回之前冻结 () 位图。
那么哪个线程访问它并不重要(当然实际的 UI 元素仍然只需要从 WPF 线程访问)
我通过如下修改解决了类似问题。
Task.Run(() =>
{
...
var bmp = ToBitMapImage(outputImage);
bmp.Freeze();
return bmp;
}...
微型 WPF 应用程序以 async/await 方式使用灰度输入参数转换图像 (BitmapImage)。
我阅读了很多实现,但没能成功:/
按键方式:
private async void btnConvertImage_ClickAsync(object sender, RoutedEventArgs e)
{
try
{
var cts = new CancellationTokenSource();
BitmapImage result = await ImageProcessing.GreyscaleAsync(orginalImage, cts.Token).ConfigureAwait(false);
imgPhotoConverted.Source = result;
}
}
灰度任务定义:
public static async Task<BitmapImage> GreyscaleAsync(BitmapImage inputBitmapImage, CancellationToken cancellationToken)
{
return await Task.Run(() =>
{
Bitmap inputBitmap = ToBitmap(inputBitmapImage);
Bitmap outputImage = new Bitmap(inputBitmap.Width, inputBitmap.Height);
for (int i = 0; i < inputBitmap.Width; i++)
{
for (int x = 0; x < inputBitmap.Height; x++)
{
cancellationToken.ThrowIfCancellationRequested();
Color imageColor = inputBitmap.GetPixel(i, x);
int grayScale = (int)((imageColor.R * 0.21) + (imageColor.G * 0.72) + (imageColor.B * 0.07));
Color newColor = Color.FromArgb(imageColor.A, grayScale, grayScale, grayScale);
outputImage.SetPixel(i, x, newColor);
}
}
return ToBitmapImage(outputImage);
}, cancellationToken);
}
在线:
imgPhotoConverted.Source = result;
抛出错误:
System.InvalidOperationException: 'The calling thread cannot access this object because a different thread owns it.'
我设法解决了:
- 我不必使用 BitmapSource,但我必须 Freeze() 输出 BitmaImage 并将 result 包装在适当的上下文中使用:
ThreadPool.QueueUserWorkItem(async delegate
{
// ThreadPool
var cts = new CancellationTokenSource();
BitmapImage result = await ImageProcessing.GreyscaleAsync(orginalImage, cts.Token);
result.Freeze();
sc.Post(delegate
{
// original context (UI)
imgPhotoConverted.Source = result;
cts.Cancel();
}, null);
}
我希望这对其他人有用。 谢谢!
您应该在 Stephen Clearys Blog 阅读更多关于 async/await 的信息。
按照那里的建议,您将找到一个非常聪明的解决方案
private async void btnConvertImage_ClickAsync(object sender, RoutedEventArgs e)
{
var originalImage = ( imgPhotoOriginal.Source as BitmapImage );
BitmapImage result = await Task.Run( () => originalImage.ToBitmap().ToGrayscale().ToBitmapImage() );
imgPhotoConverted.Source = result;
}
正在使用此扩展 class
public static class BitmapExtensions
{
public static Bitmap ToGrayscale( this Bitmap source, CancellationToken cancellationToken = default )
{
Bitmap output = new Bitmap( source.Width, source.Height );
for ( int i = 0; i < source.Width; i++ )
{
for ( int x = 0; x < source.Height; x++ )
{
cancellationToken.ThrowIfCancellationRequested();
var imageColor = source.GetPixel( i, x );
int grayScale = (int)( ( imageColor.R * 0.21 ) + ( imageColor.G * 0.72 ) + ( imageColor.B * 0.07 ) );
var newColor = System.Drawing.Color.FromArgb( imageColor.A, grayScale, grayScale, grayScale );
output.SetPixel( i, x, newColor );
}
}
return output;
}
public static Bitmap ToBitmap( this BitmapImage source )
{
using ( MemoryStream outStream = new MemoryStream() )
{
BitmapEncoder enc = new BmpBitmapEncoder();
enc.Frames.Add( BitmapFrame.Create( source ) );
enc.Save( outStream );
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap( outStream );
return new Bitmap( bitmap );
}
}
public static BitmapImage ToBitmapImage( this Bitmap source )
{
using ( var memory = new MemoryStream() )
{
source.Save( memory, ImageFormat.Png );
memory.Position = 0;
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = memory;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
bitmapImage.Freeze();
return bitmapImage;
}
}
}
我认为有一种更简单的方法可以做到这一点:在返回之前冻结 () 位图。
那么哪个线程访问它并不重要(当然实际的 UI 元素仍然只需要从 WPF 线程访问)
我通过如下修改解决了类似问题。
Task.Run(() =>
{
...
var bmp = ToBitMapImage(outputImage);
bmp.Freeze();
return bmp;
}...