This question already has answers here:
Execute task in background in WPF application
(3 answers)
Closed 2 years ago.
I am making a WPF app that needs to download multiple files from online. Currently, when I call the event which leads to the file download, my app hangs for a while as it downloads the file and only returns to functioning when the download is complete. Is there a way to make it such that the file downloads in the background and the application doesn't hang? I'm relatively new at this so the most information possible would be excellent. Thank you!
Edit: I should have been more clear. I have a function called downloadFile(), and when the main event is triggered, I need to download multiple files. Here is my code:
private void DownloadAll()
{
foreach(string moddiekey in moddiekeys)
{
download_file_by_moddiekey(moddiekey );
}
}
private async void Event(){
await Task.Run(() => { DownloadAll(); });
}
But this still hangs my application.
Edit 2: Showing download functions
private void download_file_by_url(string url)
{
try
{
string[] splits = url.Split('/');
string filename = splits[splits.Length - 1];
WebClient wc = new WebClient();
wc.DownloadFile(url, filename);
wc.Dispose();
}
catch { }
}
private void download_file_by_moddiekey(string moddiekey)
{
...
download_file_by_url("github link/" + moddict[moddiekey]);
...
}
I have a couple suggestions that may help. First looking at your code my assumption is that Event() is tied to a UI event. Calling await on a UI thread will always freeze the UI. One approach to preventing this is to use ContinueWith(). Implementing this in your case would look like:
private async void Event(){
DownloadAll().ContinueWith(OnDownloadComplete);
}
private void OnDownloadComplete(Task t)
{
if (t.IsCanceled || t.IsFaulted)
{
// indicate to user that download has failed
}
// optionally perform a task after download has been completed
}
Because the await is removed from the call to DownloadAll() the UI can continue doing it's thing well the tasks complete. After the task is completed the OnDownloadComplete() method will be called.
If you want to update the UI (e.g. display download status) from one of these methods (which would be running on a non-UI thread) you will need to send those updates back to the UI thread:
private void UpdateStatus(string message)
{
Dispatcher.Invoke(() => {
MyLabel.Text = message;
});
}
then in your download_file() method you could do something like:
private async void download_file_by_url(string url)
{
// code to download file omitted
UpdateStatus($"Downloaded file from {url}!");
}
if that doesn't work I would trying adding a ConfigureAwait call to your task. E.g:
private async void SomeMethodThatUsesAwait()
{
await DoSomething().ConfigureAwait(false);
}
Related
I'm currently trying to write a simple C#-WPF-Application that functions as a simple universal 'launcher'. For different applications we program.
It's purpose is to check the current software version of the 'real' software and if a new one is available it starts to copy the installer from a network share and runs the installer afterwards.
Then it starts the 'real' application and thats it.
The user Interface mainly consists of a startup window which shows the user the currently executed action (version check, copy, installation, startup, ...).
Now I create my view and my viewModel in the overridden StartUp method in App.cs
public override OnStartup(string[] args)
{
var viewModel = new StartViewModel();
var view = new StartView();
view.DataContext = viewModel;
view.Show();
// HERE the logic for the Launch starts
Task.Run(() => Launch.Run(args));
}
The problem is that if I don't go async here the Main Thread is blocked and I cannot update the UI. Therefore I got it working by using the Task.Run(...). This solves my problem of blocking the UI thread, but I have some problems/questions with this:
I cannot await the task, because that would block the UI again. Where to await it?
Is my concept of starting this workflow here ok in the first place?
Some update to clarify: After I show the UI to the user my logic starts to do heavy IO stuff. The possible calls I came up with are the following 3 variants:
view.Show();
// v1: completely blocks the UI, exceptions are caught
DoHeavyIOWork();
// v2: doesn't block the UI, but exceptions aren't caught
Task.Run(() => DoHeavyIOWork());
// v3: doesn't block the UI, exceptions are caught
await Task.Run(() => DoHeavyIOWork());
Currently I'm not at my work PC so i apologies for not giving you the original code. This is an on the fly created version.
I guess v1 and v2 are bad because of exceptions and the UI blocking.
I thought v3 didn't work when I tried it in my office. Now it seems to work in my local example. But I'm really not sure about v3. Because I'm using async void StartUp(...) there. Is it okay here?
I cannot await the task, because that would block the UI again. Where to await it?
await doesn't block the UI. Using await here is fine.
Is my concept of starting this workflow here ok in the first place?
I usually recommend that some UI is shown immediately when doing any asynchronous operation. Then when the async operation is complete, you can update/replace the UI.
Thanks for all the replys.
After reading all your comments and combining some of your answers I came up with the following example. It is working under all circumstances I tested.
Hopefully there is not to much wrong in your opinion.
Code behind from App.xaml.cs
public partial class App : Application
{
readonly StartViewModel viewModel = new StartViewModel();
protected override async void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var view = new StartWindow
{
DataContext = viewModel
};
view.Show(); // alternative: Run(view);
// alternative: instead of calling it here it is possible
// to move these calls as callback into the Loaded of the view.
await Task.Run(() => DoHeavyIOWork());
}
private string GenerateContent()
{
var content = new StringBuilder(1024 * 1024 * 100); // Just an example.
for (var i = 0; i < 1024 * 1024 * 2; i++)
content.Append("01234567890123456789012345678901234567890123456789");
return content.ToString();
}
private void DoHeavyIOWork()
{
var file = Path.GetTempFileName();
for (var i = 0; i < 20; i++)
{
File.WriteAllText(file, GenerateContent());
File.Delete(file);
Dispatcher.Invoke(() => viewModel.Info = $"Executed {i} times.");
}
}
}
Code in StartViewModel.cs
class StartViewModel : INotifyPropertyChanged
{
private string info;
public event PropertyChangedEventHandler PropertyChanged;
public string Info
{
get => info;
set
{
info = value;
OnPropertyChanged();
}
}
private void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
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:
the use of some 3rd party library that may be using COM
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:
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)
Call RunWorkerAsync
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.
I'm completely new to this whole Async / Await topic on C#. Despite countless questions being answered and tutorials being linked to relentlessly, I still seem to be unable to wrap my head around how to use async/await. All I want to achieve is the wpf application to return to rendering animations on the UI while it waits for an expensive method call to finish - in my following example, that'd be GetLinesAsync(), or more specifically, expensive_function() within:
void Button_Click(object sender, RoutedEventArgs e) {
MessageBox.Show(GetAMessage());
}
string GetAMessage() {
string ret = "";
foreach(string s in GetLinesAsync().Result)
ret += $"{s}\n";
return ret;
}
async Task<List<string>> GetLinesAsync() {
List<string> ret = new List<string>();
string[] ExpensiveResult = null;
if (await Task.Run(() => expensive_function(ref ExpensiveResult)) == expensive_stat.SUCCESS && ExpensiveResult != null)
{
foreach(string s in ExpensiveResult)
ret.Add(s);
}
Written like that, the application freezes completely, even though expensive_function() doesn't take THAT long to perform. Besides that I'm not sure why this happens, here's what I've understood from reading tutorials and explanations on await/async, especially the part saying you can only await methods that return either void or Task:
The line foreach(string s in GetLinesAsync().Result) should actually say foreach(string s in await GetLinesAsync().Result) - but then I'd have to mark GetMessages() async, and then I'd have to replace MessageBox.Show(GetAMessage()) with MessageBox.Show(await GetMessages()) and mark Button_Click() async.
In other words: the awaitability would creep up the call stack until it reaches a void method. Okay, but that can't be it, can it? What if, in some other scenario, there is no senseful void method to creep up to, even?
The reason why you code is blocking, is because you block it here.
foreach(string s in GetLinesAsync().Result)
You are saying UI thread wait until my expensive task has finished
Just let async flow free.
In Windows Forms and WPF, async/await has the handy property of coming back to the UI thread when the asynchronous operation you were awaiting has completed
Support for async on a void method was added specifically to support event handlers. however make sure you handle your exceptions as void will be unobserved
So you could just do this.
Note : this is an extremely simplified and sanitized example based on your code
public async void Button_Click(object sender, RoutedEventArgs e)
{
try
{
MessageBox.Show(await GetAMessage());
}
catch (Exception exception)
{
// log the nelly out of it
// or display message
}
}
public async Task<string> GetAMessage()
{
var list = await GetLinesAsync();
return string.Join("\n", list);
}
public List<string> ExpensiveFunction()
{
// ...
return new List<string>();
}
public async Task<List<string>> GetLinesAsync()
{
var result = Task.Run(() => ExpensiveFunction());
return await result;
}
I have two tasks which download an mp3 from web address inside local storage and returns timeSpan.
public async Task<TimeSpan> filedownload_Ignition_Outside()
{
Uri sourceIgnition_Outside = new Uri(TemporaryAddress.download_playCarSound+"Ignition_Outside");
//download and store file
return duration_Ignition_Outside;
}
public async Task<TimeSpan> filedownload_Idle_Outside()
{
Uri sourceIdle_Outside = new Uri(TemporaryAddress.download_playCarSound +"Idle_Outside");
StorageFile destinationFileIdle_Outside;
//download and store file
return duration_Idle_Outside;
}
Now I need to show an indeterminate progressbar in UI while downloading starts till it ends But dont know how to find the task completed?
On my NavigatedTo function I have set it as async and am calling
await downloadFile();
Now inside my downloadFile()
public async Task<TimeSpan> downloadFiles()
{
//ProgressShow
var temp= await filedownload_Ignition_Outside();
//if (filedownload_Ignition_Outside().IsCompleted)
//{
// //progressStop
//}
return temp;
}
But its not executing the stop statement as it waits asynchronously how can I get the event where both task gets finished? Can I call both tasks inside my downloadFiles() method and still able to get an event for both task completion.
Try something similar to the code given below in your OnNavigatedTo event.
ShowProgress=true;
downloadFiles().ContinueWith((sender) =>
{
this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
ShowProgress=false;
);
});
You need to run the ShowProgress=False; in UI thread
I've a scenario:
MyApp calls cameraCapture
that fires a callbackFunction
after the callbackFunction (I have a photo captured) completes, I do more stuff.
So I have to wait for callbackFunction to complete before executing another function. How could i do this?
Here my code:
private static readonly Plustek_Camera.PFNCK_EVENT staticFnCamera = fnPFNCK_EVENT;
public static bool fnPFNCK_EVENT(int iEvent, int iParam, IntPtr pUserData)
{
//capture picture and save to folder
}
//I implement callback start camera and fire a callback staticFnCamera
var _status = CameraCtrl.Start(CameraCtrl.ScanMode, CameraCtrl.Resolution, CameraCtrl.ImageFormat, CameraCtrl.Alignment, staticFnCamera);
//waiting for staticFnCamera complete make sure image produced
ReadPassPortText();
If I understand correctly, you have some camera control that provides an asynchronous API to start capturing an image, but you want to wait synchronously for that operation to complete.
If so, there are lots of different ways to accomplish what you're trying to do. One such way would be to use a TaskCompletionSource:
TaskCompletionSource<bool> source = new TaskCompletionSource<bool>();
var _status = CameraCtrl.Start(CameraCtrl.ScanMode, CameraCtrl.Resolution,
CameraCtrl.ImageFormat, CameraCtrl.Alignment,
(iEvent, iParam, pUserData) =>
{
staticFnCamera(iEvent, iParam, pUserData);
source.SetResult(true);
});
//waiting for staticFnCamera complete make sure image produced
await source.Task;
ReadPassPortText();
Note that the above uses await, which is valid only in an async method. You haven't provided enough context to show exactly how that would work in your code, but I strongly recommend following the above. That will avoid blocking the currently running thread; the async method will return at that point, letting the thread continue to run, and will be resumed at the ReadPassPortText(); statement when the operation completes.
If for some reason you simply cannot use the await in your method, you can instead simply do source.Task.Wait();. This will, of course, block the currently executing thread at that statement.
The above requires .NET 4.5. There are other approaches that work with earlier versions of .NET, but you would need to be specific about your requirements to make it worth trying to describe those.
Edit:
Since you are using .NET 4.0, and presumably Visual Studio 2010, the above won't work for you "out-of-the-box". One option is to download the Async CTP for Visual Studio, which will give you the C# 5.0 compiler that would enable the above. But if that's not feasible for you, another option is to just do what the compiler would do on your behalf, by replacing the last two lines above with the following:
source.Task.ContinueWith(task => ReadPassPortText(),
TaskScheduler.FromCurrentSynchronizationContext());
That would attach the continuation delegate that call ReadPassPortText() to the Task object from the TaskCompletionSource, specifying the current synchronization context as the source of the scheduler to use to actually run the continuation.
The method would return after calling ContinueWith() (just as it would in the await version, except that here it's written out explicitly instead of the compiler doing it for you). When the Task object is set to the completed state, the previously-registered continuation will be executed.
Note that your original question isn't very clear about the context. If the code is running in the UI thread, then using FromCurrentSynchronizationContext() is important and will ensure that the continuation is executed in the UI thread as well. Otherwise, you can probably get away without specifying a scheduler in the call to ContinueWith().
This demonstrates an async-await pattern that you can use. As Peter Duniho points out in the comments, you will have to adapt the pattern to the API that you're using. Try playing with it here at this fiddle to understand how these things work.
using System;
using System.Threading.Tasks;
public class MyApp
{
public static void Main()
{
Console.WriteLine("1. MyApp calls camera capture.");
CameraCaptureAsync().Wait();
}
public async static Task CameraCaptureAsync()
{
Console.WriteLine("2. That calls callbackFunction");
var task = CallbackFunction();
Console.WriteLine("4. In the meantime.");
Console.WriteLine("5. Do some other stuff. ");
await task;
Console.WriteLine("7. Process the " + task.Result);
DoMoreStuff();
}
public async static Task<string> CallbackFunction()
{
Console.WriteLine("3. Which takes a picture.");
await Task.Delay(100);
Console.WriteLine("6. After the callback functions completes");
return "Photograph";
}
public static void DoMoreStuff()
{
Console.WriteLine("8. Do more stuff.");
}
}
After try some implement callback waiting, i try to resolve by adding another form for capturing images (frmSecond).
frmFirst call frmSecond and waiting in 5 to 7 seconds to make sure capture completed.
after that processing ReadPassPortText()
frmFirst Code:
frmReadPassport frmReadPass = new frmReadPassport();
frmReadPass.ShowDialog();
ReadPassPortText();
frmSecondCode
private CAMERACTRL CameraCtrl = null;
//Add static for call from camera start , make sure this alive
private static Plustek_Camera.PFNCK_EVENT staticFnCamera ;
public frmReadPassport()
{
InitializeComponent();
staticFnCamera = fnPFNCK_EVENT;
}
Timer formClose = new Timer();
private void frmReadPassport_Load(object sender, EventArgs e)
{
CaptureImages();
formClose.Interval = 7000; // 7 sec
formClose.Tick += new EventHandler(formClose_Tick);
formClose.Start();
}
private void formClose_Tick(object sender, EventArgs e)
{
//free camera first
// check if camera start then stop
ReleaseResourceCamera();
staticFnCamera =null;
formClose.Stop();
formClose.Tick -= new EventHandler(formClose_Tick);
this.Dispose();
this.Close();
}
private void CaptureImages()
{
CameraCtrl = new CAMERACTRL();
CameraCtrl.LoadCameraDll();
CameraCtrl.GetDeviceList();
String temp = CameraCtrl.GetParameter();
CameraCtrl.Start(CameraCtrl.ScanMode,CameraCtrl.Resolution,CameraCtrl.ImageFormat, CameraCtrl.Alignment, staticFnCamera);
}
public static bool fnPFNCK_EVENT(int iEvent, int iParam, IntPtr UserData)
{
captureImage();
return true;
}
}