.NET C# Task await download file from AWS - c#

I'm working on a script that downloads a ~100mb file from an AWS bucket, and want to wait for it to finish before continuing.
Using https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/dotnetv3/S3/scenarios/TransferUtilityBasics/TransferUtilityBasics/TransferMethods.cs
this script, I have the following code as my function:
public static async Task<string> DownloadAWSFile(string fileName, string filePath)
{
TransferUtility fileTransferUtility = new TransferUtility(s3Client);
var success = await TransferMethods.DownloadSingleFileAsync(fileTransferUtility, Config.AWS_BucketName, fileName, filePath);
if (success)
{
return "done";
}
else
{
return "error";
}
}
Which is called by this code:
Task<string> task = AWSGET.DownloadAWSFile("filetodownload.zip", "C:\\path\\to\\download\\");
task.Wait();
if (task.Result == "done")
{
MessageBox.Show("Done Downloading", "Configuration", MessageBoxButton.OK, MessageBoxImage.Warning);
}
else
{
MessageBox.Show("Error", "Configuration", MessageBoxButton.OK, MessageBoxImage.Warning);
}
the download code works fine on its own. I click the button that runs the AWSGET.DOwnloadAWSFile function, it works fine.
Once I had that working, I changed that function to a task, as shown above.
Now, it runs, it does the download, but it doesnt trigger anything - the alerts in this case - when its done, and it freezes up my program.
Any advice you can offer is appreciated. Still pretty new to this.

This is the problem:
task.Wait();
You should not use blocking waits on async code, because it can lead to deadlocks (which you are experiencing, check out Don't Block on Async Code by Stephen Cleary). The best approach is just to await the task:
var result = await AWSGET.DownloadAWSFile(...);
Also possibly you should consider using ConfigureAwait(false) inside the download method, JIC:
var success = await TransferMethods.DownloadSingleFileAsync(...).ConfigureAwait(false);

Related

Xamarin Forms - Wait for async method to complete before continuing

In my Xamarin.Forms application, I have code that opens a popup page (using the Rg Popups plugin) which executes a call to my API that inserts some data on the database.
I need the async call for the popup to open to wait until the popup page's task finishes then execute another async call which updates a ListView in the current view.
However, the popup's async task does not wait until the popup has finished working to continue to the next task, which means the view isn't updated
Here's the code in question:
bool jobTask = await CloseCurOpenJob();
if (jobTask == true)
{
await FillDocuments();
}
await Navigation.PushPopupAsync(new JobClosePopupPage(Base.activeJobId));
return true;
private async Task<string> FillDocuments()
{
HttpClient client = new HttpClient();
try
{
if (jobid > 0)
{
var response = await client.GetStringAsync(myApiUri);
var DocumentsList = JsonConvert.DeserializeObject<List<DocumentHeader>>(response);
Documents = new ObservableCollection<DocumentHeader>((IEnumerable<DocumentHeader>)DocumentsList);
DocumentCount = Documents.Count();
DocumentSubtotal = Documents.Sum(instance => instance.TotalQuantity);
}
return "OK";
}
catch (Exception e)
{
return e.Message;
}
}
And here's the code in the popup page's ViewModel
private async Task<bool> CloseJob(int jobid)
{
HttpClient client = new HttpClient();
try
{
var response = await client.PostAsync(myAPIUri);
string responseString = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
await CheckGetOpenJob();
return true;
}
else
{
await Application.Current.MainPage.DisplayAlert(Base.applicationTitle, responseString, "OK");
return false;
}
}
catch (Exception e)
{
await Application.Current.MainPage.DisplayAlert(Base.applicationTitle, e.Message, "OK");
return false;
}
finally
{
await Navigation.PopPopupAsync();
}
}
I have tried using Task.Run and Task.Delay but the result is still the same - the FillDocuments method runs after the popup has been shown, not after it has been dismissed.
The Task.Wait() and Task.Result will make the program wait for the async task method competed.
If the CloseCurOpenJob() is like public async Task<bool> CloseCurOpenJob(){} You can try the following code:
// Both are applicable to simple Tasks:
bool jobTask = CloseCurOpenJob().Result;
or
Task<bool> result = CloseCurOpenJob();
result.Wait();
bool jobTask = result.Result;
According to your description, you want update the popup when it show. But you sayed that the popup will not show when you used the .Wait() method.
Generally speaking, it shouldn't happen. But you can try to use the Thread.Sleep() such as:
bool jobTask = await CloseCurOpenJob();
Thread.Sleep(1000); //make the thread to wait for 1s and the async task usually completes in 1s
if (jobTask == true)
{
await FillDocuments();
}
But this way seems not elegant, you can post the code about the CloseCurOpenJob() method. In addition, if the CloseCurOpenJob() is public async bool CloseCurOpenJob(), you can make the thread wait without the await. Such as
bool jobTask = CloseCurOpenJob();
I wanted to share something I've figured out:
The problem was not in the code, but the flow of it.
Namely, I needed one page to dismiss a popup while ANOTHER page was working.
The solution I think I found - it has not been tested - is as follows:
Show the popup
Do the processing in the main page's thread after showing the popup - as it's asynchronous, the code will continue to the task.
Dismiss the popup in the main page's code (using the PopAllPopup method of the Rg plugin).
So...
bool jobTask = await CloseCurOpenJob();
if (jobTask == true)
{
await FillDocuments();
}
Would become
await Navigation.PushPopupAsync(new JobClosePopupPage(Base.activeJobId));
bool jobTask = MethodThatProcessesTheData().Result;
await Navigation.PopAllPopupAsync();
I think this is how it's supposed to go...

Inform that a long running async task is in progress - the right way

I have a console program which sends async HTTP requests to an external web API. (HttpClient.GetAsync());)
These tasks can take several minutes to complete - during which I'd like to be able to show to the user that the app is still running - for example by sending Console.WriteLine("I ain't dead - yet") every 10 seconds.
I am not sure how to do it right, without the risk of hiding exceptions, introducing deadlocks etc.
I am aware of the IProgress<T>, however I don't know whether I can introduce it in this case. I am await a single async call which does not report progress. (It's essentially an SDK which calls httpClient GetAsync() method
Also:
I cannot set the GUI to 'InProgress', because there is no GUI, its a console app - and it seems to the user as if it stopped working if I don't send an update message every now and then.
Current idea:
try
{
var task = httpClient.GetAsync(uri); //actually this is an SDK method call (which I cannot control and which does not report progress itself)
while (!task.IsCompleted)
{
await Task.Delay(1000 * 10);
this.Logger.Log(Verbosity.Verbose, "Waiting for reply...");
}
onSuccessCallback(task.Result);
}
catch (Exception ex)
{
if (onErrorCallback == null)
{
throw this.Logger.Error(this.GetProperException(ex, caller));
}
this.Logger.Log(Verbosity.Error, $"An error when executing command [{action?.Command}] on {typeof(T).Name}", ex);
onErrorCallback(this.GetProperException(ex, caller));
}
Let me tidy this code up a bit for you
async Task Main()
{
var reporter = new ConsoleProgress();
var result = await WeatherWaxProgressWrapper(() => GetAsync("foo"), reporter);
Console.WriteLine(result);
}
public async Task<int> GetAsync(string uri)
{
await Task.Delay(TimeSpan.FromSeconds(10));
return 1;
}
public async Task<T> WeatherWaxProgressWrapper<T>(Func<Task<T>> method, System.IProgress<string> progress)
{
var task = method();
while(!task.IsCompleted && !task.IsCanceled && !task.IsFaulted)
{
await Task.WhenAny(task, Task.Delay(1000));
progress.Report("I ain't dead");
}
return await task;
}
public class ConsoleProgress : System.IProgress<string>
{
public void Report(string value)
{
Console.WriteLine(value);
}
}
You could have a never-ending Task as a beacon that signals every 10 sec, and cancel it after the completion of the long running I/O operation:
var beaconCts = new CancellationTokenSource();
var beaconTask = Task.Run(async () =>
{
while (true)
{
await Task.Delay(TimeSpan.FromSeconds(10), beaconCts.Token);
Console.WriteLine("Still going...");
}
});
await LongRunningOperationAsync();
beaconCts.Cancel();
You are looking for System.Progress<T>, a wonderful implementation of IProgress.
https://learn.microsoft.com/en-us/dotnet/api/system.progress-1
You create an object of this class on the "UI thread" or the main thread in your case, and it captures the SynchronizationContext for you. Pass it to your worker thread and every call to Report will be executed on the captured thread, you don't have to worry about anything.
Very useful in WPF or WinForms applications.

Async upload to Azure isn't blocking even though calling Task.WaitAll(task)

I am uploading a file to Azure. The code uploads the file fine, but my page refreshes before it's finalized and shows the old image. I can refresh the page manually and it shows the new image. Why isn't my method waiting for the task to finish?
public static bool Upload(Stream image, String id)
{
try {
var key = String.Format("{0}.png", id);
image.Position = 0;
var container = new CloudBlobContainer(new Uri(string.Format("{0}/{1}", Host, Container)), Credentials);
var blob = container.GetBlockBlobReference(key);
blob.Properties.ContentType = "image/png";
Task task = Task.Run(() => { blob.UploadFromStreamAsync(image); });
Task.WaitAll(task);
}
catch {
return false;
}
return true;
}
ANSWER: So thanks to aleksey.berezan. The answer turned out to be not even using the task.
So this:
Task task = Task.Run(() => { blob.UploadFromStreamAsync(image); });
Task.WaitAll(task);
Became this:
Task.WaitAll(blob.UploadFromStreamAsync(image));
And everything worked perfectly!
This guy:
blob.UploadFromStreamAsync(image);
starts new task.
Hence this guy:
Task.Run(() => { blob.UploadFromStreamAsync(image); });
just starts task which starts task. So that this code:
Task task = Task.Run(() => { blob.UploadFromStreamAsync(image); });
Task.WaitAll(task);
will just wait until upload-task gets fired(which happens kinda immediately) but not for the completion of upload-task.
To fix the situation you'll have to write:
Task.WaitAll(blob.UploadFromStreamAsync(image));
You're waiting for the task started with Task.Run. You want to wait for UploadFromStreamAsync. In fact I don't see why you need Task.Run here. It only makes things slower. You transfer work to the thread-pool, then wait for it to complete.
Just call the synchronous version of UploadFromStreamAsync if there is one. Or, call Wait on the task that UploadFromStreamAsync returns (less preferable).
You might want to revise your exception handling. You'll never find out about bugs in this method because all exceptions are thrown away.

File access from a background task in a windows store app

I can't seem to read a file from a background task in a windows store app. Here's the code that reads the file content:
async private static Task<string> ReadAsync(string FileName)
{
var folder = ApplicationData.Current.LocalFolder;
var file = await folder.GetFileAsync(FileName);
Windows.Storage.Streams.IRandomAccessStreamWithContentType inputStream = null;
try
{
inputStream = await file.OpenReadAsync();
}
catch (Exception ex)
{
throw (ex);
}
string content = string.Empty;
using (Stream stream = inputStream.AsStreamForRead())
{
using (StreamReader reader = new StreamReader(stream))
{
try
{
// *** program exits on this line
content = await Task.Run(() => reader.ReadToEnd());
}
catch(Exception ex)
{
// no error is caught
content = ex.Message;
}
}
}
return content;
}
The program exits on the line that calls ReadToEnd() on the StreamReader - no error is caught in the try catch block. In the output window I get:
The program '[8968] backgroundTaskHost.exe: Managed (v4.0.30319)' has exited with code 1 (0x1)
Is it possible to access files a background task? If so, where am I going wrong?
It would be helpful if you posted your IBackgroundTask code. Without seeing it I suspect you didn't call GetDeferral() inside it, e.g.:
public async void Run(IBackgroundTaskInstance taskInstance)
{
var deferral = taskInstance.GetDeferral();
var contents = await ReadAsync("MyFile.txt");
deferral.Complete();
}
You need to call GetDeferral() whenever you are making asynchronous calls inside your background task. This way you tell the runtime it needs to wait for the asynchronous call to complete and not stop the background task as soon as Run exits.
Once you're done, i.e. usually at the end of your Run method, you need to call Complete() on the deferral instance to notify the runtime that you're done.
There are already system classes (DataReader) to read file asynchronously, so I'm not sure why you decided to write your own.

Task.ContinueWith execution order

Apparently, I'm not understanding how to use the ContinueWith method. My goal is to execute a task, and when complete, return a message.
Here's my code:
public string UploadFile()
{
if (Request.Content.IsMimeMultipartContent())
{
//Save file
MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(HttpContext.Current.Server.MapPath("~/Files"));
Task<IEnumerable<HttpContent>> task = Request.Content.ReadAsMultipartAsync(provider);
string filename = "Not set";
task.ContinueWith(o =>
{
//File name
filename = provider.BodyPartFileNames.First().Value;
}, TaskScheduler.FromCurrentSynchronizationContext());
return filename;
}
else
{
return "Invalid.";
}
}
The variable "filename" always returns "Not set". It seems the code within the ContinueWith method is never called. (It does get called if I debug through it line by line in VS.)
This method is being called in my ASP.NET Web API controller / Ajax POST.
What am I doing wrong here?
If you're using an asynchronous operation, the best approach would be to make your operation asynchronous as well, otherwise you'll lose on the advantages of the async call you're making. Try rewriting your method as follows:
public Task<string> UploadFile()
{
if (Request.Content.IsMimeMultipartContent())
{
//Save file
MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(HttpContext.Current.Server.MapPath("~/Files"));
Task<IEnumerable<HttpContent>> task = Request.Content.ReadAsMultipartAsync(provider);
return task.ContinueWith<string>(contents =>
{
return provider.BodyPartFileNames.First().Value;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
else
{
// For returning non-async stuff, use a TaskCompletionSource to avoid thread switches
TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
tcs.SetResult("Invalid.");
return tcs.Task;
}
}
The reasons for your variable not being set are:
the tasks are instantiated, but not run.
even if the tasks ran, the function would probably return before they finished running so, it would still return "Not set". The fix for this is waiting for the final task (the one setting fileName) to finish.
Your code could be fixed like this:
public string UploadFile()
{
if (Request.Content.IsMimeMultipartContent())
{
//Save file
MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(HttpContext.Current.Server.MapPath("~/Files"));
Task<IEnumerable<HttpContent>> task = Request.Content.ReadAsMultipartAsync(provider);
string filename = "Not set";
var finalTask = task.ContinueWith(o =>
{
//File name
filename = provider.BodyPartFileNames.First().Value;
}, TaskScheduler.FromCurrentSynchronizationContext());
task.Start();
finalTask.Wait();
return filename;
}
else
{
return "Invalid.";
}
}
The additions are the following:
assigned the return value of task.ContinueWith to a variable called finalTask. We need this task, because we'll wait for it to finish
started the task (the task.Start(); line)
waited for the final task to finish before returning (finalTask.Wait();)
If possible, please consider not implementing this asynchronously, because in the end it's synchronous (you're waiting for it to finish) and the current implementation adds complexity that could probably be avoided.
Consider doing something along these lines (if possible):
public string UploadFile()
{
if (Request.Content.IsMimeMultipartContent())
{
//Save file
MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(HttpContext.Current.Server.MapPath("~/Files"));
Request.Content.ReadAsMultipart(provider); // don't know if this is really valid.
return provider.BodyPartFileNames.First().Value;
}
else
{
return "Invalid.";
}
}
Disclaimer: I have not actually executed the above code; I just wrote it to illustrate what should be done.
You should return the type Task<T> from the method, in this case it would be a Task<string>.
You are using an asynch operation. If you want to wait for its completion, you have to use the Wait method otherwise of your task:
task.ContinueWith(o =>
{
//File name
filename = provider.BodyPartFileNames.First().Value;
).Wait();
return filename;
Edit:
Some asynch methods start the task as soon as it is created, whereas other ask you to explicitly start them. You have to consult the documentation for each to be sure. In this case, it appears the task does start automatically.

Categories