[英]Asynchronously Wait for a thread to finish
好吧,我有一个功能,从PC上截取屏幕截图,但不幸的是它阻止了主UI,所以我决定对它进行异步[线程调用];但是,我发现在返回Bitmap之前等待Thread的结果很麻烦。
这是我的代码:
/// <summary>
/// Asynchronously uses the snapshot method to get a shot from the screen.
/// </summary>
/// <returns> A snapshot from the screen.</returns>
private Bitmap SnapshotAsync()
{
Bitmap image = null;
new Thread(() => image = Snapshot()).Start();
while (image == null)
{
new Thread(() => Thread.Sleep(500)).Start(); //Here i create new thread to wait but i don't think this is a good way at all.
}
return image;
}
/// <summary>
/// Takes a screen shots from the computer.
/// </summary>
/// <returns> A snapshot from the screen.</returns>
private Bitmap Snapshot()
{
var sx = Screen.PrimaryScreen.Bounds.Width;
var sy = Screen.PrimaryScreen.Bounds.Height;
var shot = new Bitmap(sx, sy, PixelFormat.Format32bppArgb);
var gfx = Graphics.FromImage(shot);
gfx.CopyFromScreen(0, 0, 0, 0, new Size(sx, sy));
return shot;
}
虽然上面的方法是我想要的异步工作,但我相信它可以改进。 特别是我执行数百个线程等待结果的方式,我确信这种方式并不好。
所以我真的需要任何一个看一下代码并告诉我如何改进代码的人。
[注意我使用的是.NET 3.5]
并提前感谢。
在Eve和SiLo的帮助下解决的问题是最好的2个答案
- 1:
> private void TakeScreenshot_Click(object sender, EventArgs e)
> {
> TakeScreenshotAsync(OnScreenshotTaken);
> }
>
> private static void OnScreenshotTaken(Bitmap screenshot)
> {
> using (screenshot)
> screenshot.Save("screenshot.png", ImageFormat.Png);
> }
>
> private static void TakeScreenshotAsync(Action<Bitmap> callback)
> {
> var screenRect = Screen.PrimaryScreen.Bounds;
> TakeScreenshotAsync(screenRect, callback);
> }
>
> private static void TakeScreenshotAsync(Rectangle bounds, Action<Bitmap> callback)
> {
> var screenshot = new Bitmap(bounds.Width, bounds.Height,
> PixelFormat.Format32bppArgb);
>
> ThreadPool.QueueUserWorkItem((state) =>
> {
> using (var g = Graphics.FromImage(screenshot))
> g.CopyFromScreen(bounds.X, bounds.Y, 0, 0, bounds.Size);
>
> if (callback != null)
> callback(screenshot);
> });
> }
- 2:
> void SnapshotAsync(Action<Bitmap> callback)
> {
> new Thread(Snapshot) {IsBackground = true}.Start(callback);
> }
> void Snapshot(object callback)
> {
> var action = callback as Action<Bitmap>;
> var sx = Screen.PrimaryScreen.Bounds.Width;
> var sy = Screen.PrimaryScreen.Bounds.Height;
> var shot = new Bitmap(sx, sy, PixelFormat.Format32bppArgb);
> var gfx = Graphics.FromImage(shot);
> gfx.CopyFromScreen(0, 0, 0, 0, new Size(sx, sy));
> action(shot);
> }
用法,例如,通过按钮的单击:
void button1_Click(object sender, EventArgs e) { SnapshotAsync(bitmap => MessageBox.Show("Copy successful!")); }
async
/ await
关键字完全符合您的要求,非常优雅。
以下是我将您的方法转换为正确模式的方法:
private static async Task<Bitmap> TakeScreenshotAsync()
{
var screenRect = Screen.PrimaryScreen.Bounds;
return await TakeScreenshotAsync(screenRect);
}
private static async Task<Bitmap> TakeScreenshotAsync(Rectangle bounds)
{
var screenShot = new Bitmap(bounds.Width, bounds.Height,
PixelFormat.Format32bppArgb);
// This executes on a ThreadPool thread asynchronously!
await Task.Run(() =>
{
using (var g = Graphics.FromImage(screenShot))
g.CopyFromScreen(bounds.X, bounds.Y, 0, 0, bounds.Size);
});
return screenShot;
}
然后你会做这样的事情:
private async void TakeScreenshot_Click(object sender, EventArgs e)
{
var button = sender as Button;
if(button == null) return;
button.Enabled = false;
button.Text = "Screenshoting...";
var bitmap = await TakeScreenshotAsync();
bitmap.Save("screenshot.png", ImageFormat.Png);
button.Text = "Take Screenshot";
button.Enabled = true;
}
您可以使用基于事件的异步模式:
void SnapshotAsync(Action<Bitmap> callback)
{
new Thread(Snapshot) {IsBackground = true}.Start(callback);
}
void Snapshot(object callback)
{
var action = callback as Action<Bitmap>;
var sx = Screen.PrimaryScreen.Bounds.Width;
var sy = Screen.PrimaryScreen.Bounds.Height;
var shot = new Bitmap(sx, sy, PixelFormat.Format32bppArgb);
var gfx = Graphics.FromImage(shot);
gfx.CopyFromScreen(0, 0, 0, 0, new Size(sx, sy));
action(shot);
}
用法,例如,通过按钮的单击:
void button1_Click(object sender, EventArgs e)
{
SnapshotAsync(bitmap => MessageBox.Show("Copy successful!"));
}
正如作者所要求的那样,它不会阻止原始线程。 如果你必须通过回调操作UI,请小心,记得使用Invoke
及其等价物。
编辑:阅读SiLo对一些可以应用于上述代码的良好实践和优化的评论。
我刚刚看到你关于使用3.5而不是4.5的编辑。 这太糟糕了,但绝对有可能。 我已经创建了第二个答案,因此使用async
/ await
可以使用第一个答案作为示例。
现在为您的解决方案,它真的没有太大的不同:
private void TakeScreenshot_Click(object sender, EventArgs e)
{
TakeScreenshotAsync(OnScreenshotTaken);
}
private static void OnScreenshotTaken(Bitmap screenshot)
{
using (screenshot)
screenshot.Save("screenshot.png", ImageFormat.Png);
}
private static void TakeScreenshotAsync(Action<Bitmap> callback)
{
var screenRect = Screen.PrimaryScreen.Bounds;
TakeScreenshotAsync(screenRect, callback);
}
private static void TakeScreenshotAsync(Rectangle bounds, Action<Bitmap> callback)
{
var screenshot = new Bitmap(bounds.Width, bounds.Height,
PixelFormat.Format32bppArgb);
ThreadPool.QueueUserWorkItem((state) =>
{
using (var g = Graphics.FromImage(screenshot))
g.CopyFromScreen(bounds.X, bounds.Y, 0, 0, bounds.Size);
if (callback != null)
callback(screenshot);
});
}
对不起,但这在逻辑上并不太聪明你在这里尝试。
Gratulations。 到目前为止这是有道理的。
现在出现的部分你不想告诉任何你尝试过的人:
回到我们的开始 - 你阻止UI线程。 什么都没有实现。 你基本上逻辑上最终在你开始的同一个地方。
好的,解决方案:
将此处理为状态机问题(UI处于“工作”或“等待命令”状态),因此您不会阻止。 这是处理它的唯一方法 - 因为如果你等待执行完成,那么整个异步orpeation是无用的。
你无法启动一个方法,然后等待处理完成阻塞线程 - 如果你尝试过,那么整个异步操作是一个无用的命题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.