简体   繁体   中英

How to fix GDI+ Errors when downloading files?

I have been making a client that installs a program I am also making. The problem is when I go to download the files. Sometimes, it gets stuck. I get thrown a error stating

System.Runtime.InteropServices.ExternalException: 'A generic error occurred in GDI+.'

...and my UI freaks out (Buttons go blank, labels disapper, images go missing, ect). I have researched this error and it seems it happens with images, but I am downloading zip files that contain no images. I have found that it might have something to do with 5 files I am extracting from the zip files but thats not entirely accurate because it doesnt always happen and I have no real way to determine EXACTLY whats causing it. I suspect its because I cannot download so much in a short period of time but I do not know if this is why exactly either.

Also to add to this, the files still complete downloading when in debug mode, they ever continue through the async process, awaiting properly and everything.

I have tried narrowing down what file is causing it but I dont have any evidence to support it is a specific file. I have also tried spliting up the zip files to see if its the size of how much Im downloading at once, still no luck.

These are the download functions. The RunWorkerTaskAsync() is a custom reference I created to allow a worker to be "awaited". I privide the code below.(I take no credit as its pieces of code I have pulled from others)

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

I expect the files to download without the form causing UI glitches and crashing.

EDIT: Link to author's client code (taken from comment below)


This is a summary of my comments beneath the OP's question

System.Runtime.InteropServices.ExternalException: 'A generic error occurred in GDI+.'

So the "interop" error implies some form of component object model (COM) problem and the things that stick out are:

  1. the use of some 3rd party library that may be using COM
  2. your RunWorkerTaskAsync extension method seems to be making multiple calls to BackgroundWorker.RunWorkerAsync without first checking that the worker is busy.

BackgroundWorker.RunWorkerAsync() is a void , it does not return a Task and so can't be used in async/await . Therefore your extension method is essentially kicking off a background worker without waiting for it to complete. Your extension method RunWorkerTaskAsync() (which isn't entirely async ) returns immediately to those that called it.

You need to for the worker to complete before calling RunWorkerAsync again.

A possible fix:

  1. Inside your extension method, check BackgroundWorker.IsBusy before telling it to run. (A better way is to wait for RunWorkerCompleted and kicking off a new one there)

  2. Call RunWorkerAsync

  3. Because you want to wait for this "task" to complete before returning control to say DownloadMods() , your extension method will need to monitor RunWorkerCompleted . This is kinda ugly as it goes against the original best practices of BackgroundWorker where everything is event-driven.

Alternatives

Considering you are using async/await anyway, why use BackgroundWorker at all? Consider wrapping up the essence of your extension method into a new method and call it via Task.Run() .

You can still have a async Task ExtractFilesAsync method that runs in a child task (because we used Task.Run() it will also be a child thread) can report progress.

Something like (pseudo code):

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

By the way, you shouldn't call MessageBox.Show or update the UI directly in a child thread, even if the call is a dialog with its own message pump.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM