我一直在制作一个安装我也在制作的程序的客户端。问题是当我去下载文件时。有时,它会卡住。抛出错误提示
System.Runtime.InteropServices.ExternalException:“ GDI +中发生一般错误。”
...然后我的UI出现异常(按钮变为空白,标签消失,图像丢失,等等)。我研究了此错误,似乎在图像中发生了此错误,但是我正在下载不包含图像的zip文件。我发现它可能与我从zip文件中提取的5个文件有关,但是那并不完全准确,因为它并不总是发生,而且我也没有真正的方法来确定到底是什么原因。我怀疑这是因为我无法在短时间内下载太多内容,但我不知道这到底是为什么。
此外,在调试模式下,文件仍会完成下载,它们会继续执行异步过程,等待适当的一切。
我曾尝试缩小导致它的文件的大小,但是我没有任何证据支持它是特定文件。我还尝试过拆分zip文件,以查看一次下载的Im大小是否仍然没有运气。
这些是下载功能。
RunWorkerTaskAsync()
是我创建的自定义引用,用于“等待”工作人员。我将以下代码设为私有。(我不认为这是我从他人那里获取的代码段)
private async Task DownloadLibs()
{
Response.Text = "Updating Libraries...";
this.Update();
string url = @"http://akumamc.com/AkumaMC/Libraries.zip";
if (!string.IsNullOrEmpty(url))
{
Uri uri = new Uri(url);
string fileName = System.IO.Path.GetFileName(uri.AbsolutePath);
await DLclient.DownloadFileTaskAsync(uri, @"C:\temp\" + fileName);
DLclient.Dispose();
}
FileZipName = @"C:\temp\Libraries.zip";
FileZipPath = @"C:\temp\.minecraft";
Response.Text = "Extracting Libraries...";
this.Update();
await extractFile.RunWorkerTaskAsync();
}
private async Task DownloadMods()
{
Response.Text = "Updating Mods (1/2)...";
this.Update();
string url = @"http://akumamc.com/AkumaMC/Mods.zip";
if (!string.IsNullOrEmpty(url))
{
Uri uri = new Uri(url);
string fileName = System.IO.Path.GetFileName(uri.AbsolutePath);
await DLclient.DownloadFileTaskAsync(uri, @"C:\temp\" + fileName);
DLclient.Dispose();
}
FileZipName = @"C:\temp\Mods.zip";
FileZipPath = @"C:\temp\.minecraft";
Response.Text = "Extracting Mods (1/2)...";
this.Update();
await extractFile.RunWorkerTaskAsync();
}
private async Task DownloadExtras()
{
Response.Text = "Updating Mods (2/2)...";
this.Update();
string url = @"http://akumamc.com/AkumaMC/Mods2.zip";
if (!string.IsNullOrEmpty(url))
{
Uri uri = new Uri(url);
string fileName = System.IO.Path.GetFileName(uri.AbsolutePath);
await DLclient.DownloadFileTaskAsync(uri, @"C:\temp\" + fileName);
DLclient.Dispose();
}
FileZipName = @"C:\temp\Mods2.zip";
FileZipPath = @"C:\temp\.minecraft";
Response.Text = "Extracting Mods (2/2)...";
this.Update();
await extractFile.RunWorkerTaskAsync();
}
RunWorkerTaskAsync:
public static Task<object> RunWorkerTaskAsync(this BackgroundWorker backgroundWorker)
{
var tcs = new TaskCompletionSource<object>();
RunWorkerCompletedEventHandler handler = null;
handler = (sender, args) =>
{
if (args.Cancelled)
tcs.TrySetCanceled();
else if (args.Error != null)
tcs.TrySetException(args.Error);
else
tcs.TrySetResult(args.Result);
};
backgroundWorker.RunWorkerCompleted += handler;
try
{
backgroundWorker.RunWorkerAsync();
}
catch
{
backgroundWorker.RunWorkerCompleted -= handler;
throw;
}
return tcs.Task;
}
我希望文件下载时不会出现导致UI故障和崩溃的表格。
编辑:Link到作者的客户代码(摘自下面的评论)
答案 0 :(得分:0)
System.Runtime.InteropServices.ExternalException:“ GDI +中发生一般错误。”
因此“ interop” 错误表示某种形式的组件对象模型(COM)问题,而突出的问题是:
RunWorkerTaskAsync
扩展方法似乎正在多次调用BackgroundWorker.RunWorkerAsync
,而没有先检查工作人员是否忙碌。 BackgroundWorker.RunWorkerAsync()
是void
,它不返回Task
,因此不能在async/await
中使用。因此,您的扩展方法实际上是在不等待后台工作完成的情况下启动后台工作人员。您的扩展方法RunWorkerTaskAsync()
(并不完全是async
)会立即返回给调用它的人。
您需要让工作人员完成后才能再次致电RunWorkerAsync
。
可能的解决方法:
在扩展方法中,检查BackgroundWorker.IsBusy
,然后再告诉它运行。 (更好的方法是等待RunWorkerCompleted
并在那里开始新的旅程)
致电RunWorkerAsync
由于要在返回“ DownloadMods()
”控制权之前等待此“任务”完成,因此扩展方法将需要监视RunWorkerCompleted
。这有点丑陋,因为它违反了BackgroundWorker
的原始最佳实践,在后者中,一切都是由事件驱动的。
考虑到您仍在使用async/await
,为什么还要使用BackgroundWorker
?考虑将扩展方法的本质包装到一个新方法中,并通过Task.Run()
对其进行调用。
您仍然可以在子任务中运行一个async Task ExtractFilesAsync
方法(因为我们使用了Task.Run()
它将也是一个子线程)可以报告进度。
类似(伪代码)的
await Task.Run ( async () => await
UnzipFilesAsync ( p =>
{
myProgressBar.BeginInvoke (new Action( () =>
myprogressBar.Progress = p; ));
});
.
.
.
UnzipFilesAsync (Action<int> progressCallback)
{
.
.
.
int percent = ...;
progressCallback (percent);
}
Tell me more about async progress bar updates
顺便说一句,即使调用是带有自己的消息泵的对话框,也不应直接在子线程中调用MessageBox.Show
或更新UI。