wait for event handler of a single thread to execute - c#

I am uploading files to S3. But the file upload fails silently without any error. So I have written an event handler to keep track of the upload.
Below is my function for file upload.
I want to wait for event handler till it completes its execution. Please note it is synchronous file upload.
public async Task<ActionResult> UploadBatchDocuments(UploadViewModel model)
{
TransferUtility transfer = new TransferUtility(client);
foreach (var file in model.Documents)
{
TransferUtilityUploadRequest request = new TransferUtilityUploadRequest()
{
BucketName = bucketName,
CannedACL = S3CannedACL.PublicRead,
Key = string.Format(file.FilePath + "/{0}", fileName),
InputStream = file.File.InputStream,
};
await Task.Run(()=>request.UploadProgressEvent += uploadRequest_UploadPartProgressEvent);
await Task.Run(() => transfer.Upload(request));
}
}
Below is my event handler which runs for every file upload,
public async void uploadRequest_UploadPartProgressEvent(object sender, UploadProgressArgs e)
{
if (e.PercentDone == 100)
{
var subs = ((Amazon.S3.Transfer.TransferUtilityUploadRequest)sender).Key.Split('/');
var fileName = subs[subs.Length - 1];
for (int i = 0; i < tempModel.Documents.Count; i++)
{
if (tempModel.Documents[i].File.FileName.Equals(fileName))
{
tempModel.Documents[i].isUploaded = true;
}
}
}
}
My problem is:Control keeps switching between UploadBatchDocuments and uploadRequest_UploadPartProgressEvent(which is an event handler).
Required:I want the control to wait in UploadBatchDocuments to wait for the thread to complete in uploadRequest_UploadPartProgressEvent

Related

How to add a completed event when using httpclient to download files?

private async Task Download()
{
FilesDownoads downloads = new FilesDownloads(#"d:\testfiles");
downloads.PrepareLinks();
using var client = new HttpClient();
for (int i = 0; i < files.Count; i++)
{
try
{
using var s = await client.GetStreamAsync(files[i]);
using var fs = new FileStream(#"e:\files1\file" + i.ToString() + ".txt", FileMode.OpenOrCreate);
await s.CopyToAsync(fs);
}
catch { }
}
}
private async void button1_Click(object sender, EventArgs e)
{
await Download();
}
the goal is to download each file and when the download is completed for each file to do something with the file in the completed event.
i tried to google but still not sure how to add the progress and completed events (mostly the completed ).

Reading large txt file async and reporting progress in progressbar WPF C# [duplicate]

This question already has answers here:
Read/Write text file progressbar in C#
(1 answer)
How to show progress of reading from a file and writing to a database
(1 answer)
Closed 1 year ago.
I am trying to read a large txt file (>50MB) asynchronously and while it is being read, report the progress on the UI progressbar and have the option to cancel the process. So far I have read and processed the file async as I wanted but I could not solve the progressbar part.
public static async Task<string> ReadTxtAsync(string filePath)
{
try
{
using (var reader = File.OpenText(filePath))
{
var content = await reader.ReadToEndAsync();
return content;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return null;
}
}
public static async Task<Dictionary<string, int>> OpenTxtAsync()
{
Dictionary<string, int> uniqueWords = new Dictionary<string, int>();
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "Text Documents (*.txt)|*.txt";
string content = null;
try
{
if (openFileDialog.ShowDialog() == true)
{
string filePath = openFileDialog.FileName.ToString();
if (openFileDialog.CheckFileExists && new[] { ".txt" }.Contains(Path.GetExtension(filePath).ToLower()) && filePath != null)
{
Task<string> readText = ReadTxtAsync(filePath);
content = await readText;
uniqueWords = WordExtractor.CountWords(ref content);
}
else MessageBox.Show("Please use .txt format extension!");
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return uniqueWords;
}
private async void LoadFileButtonClick(object sender, RoutedEventArgs e)
{
Task<Dictionary<string, int>> dictionaryContent = TextFileLoader.OpenTxtAsync();
uniqueWords = await dictionaryContent;
UpdateListView();
}
How can I check where ReadToEndAsync() is currently? How can I get it to continously update the progressbar and how can I cancel it?
EDIT:
Thanks to #emoacht I managed to get the progressbar to update correctly and display its percentage. The only thing that remains is to cancel the task, which I tried according to a Tim Corey video, but it did not work on my code.
public static async Task<string> ReadTextAsync(string filePath, IProgress<(double current, double total)> progress, CancellationToken cancellationToken)
{
using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
using var reader = new StreamReader(stream);
var readTask = reader.ReadToEndAsync();
cancellationToken.ThrowIfCancellationRequested();
var progressTask = Task.Run(async () =>
{
while (stream.Position < stream.Length)
{
await Task.Delay(TimeSpan.FromMilliseconds(100));
progress.Report((stream.Position, stream.Length));
}
});
await Task.WhenAll(readTask, progressTask);
return readTask.Result;
}
try
{
Task<string> readText = TextFileLoader.ReadTextAsync(filePath, progress, cts.Token);
content = await readText;
LabelProgress.Content = "Done Reading! Now creating wordlist...";
}
catch (OperationCanceledException)
{
LabelProgress.Content = "File laden wurde abgebrochen";
}
I have a buttonClick Event for cancel cts.Cancel(); but the only part where it works is the Dictionary creation. If I place the cancellationToken.ThrowIfCancellationRequested(); into the progressbar update part, it stops only the update, the stream reading still continues. If I place is right below var readTask = reader.ReadToEndAsync(); it does nothing.
You can get the current position while reading by checking Stream.Position property at regular interval. The following method will check the current position once per 100 milliseconds and report it by current value of progress parameter. To use this method, instantiate Progess<(double current, double total)> and subscribe to its ProgressChanged event.
public static async Task<string> ReadTextAsync(string filePath, IProgress<(double current, double total)> progress)
{
using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
using var reader = new StreamReader(stream);
var readTask = reader.ReadToEndAsync();
var progressTask = Task.Run(async () =>
{
while (stream.Position < stream.Length)
{
await Task.Delay(TimeSpan.FromMilliseconds(100));
progress.Report((stream.Position, stream.Length));
}
});
await Task.WhenAll(readTask, progressTask);
return readTask.Result;
}

Functions aren't finishing before starting the next

I have a youtube uploader, and I am generating a video from an audio file, which works fine, but when I am uploading to Youtube the program still runs when I am trying to wait for it to finish uploading before repeating
Here I generate a video:
private void button2_Click(object sender, EventArgs e)
{
if (status.Text == "Stopped")
{
if (!generatearticle.IsBusy)
{
// started
status.Text = "Started";
status.ForeColor = System.Drawing.Color.Green;
start.Text = "Stop Generating";
generatearticle.RunWorkerAsync();
}
}
else
{
if(generatearticle.IsBusy)
{
generatearticle.CancelAsync();
// started
status.Text = "Stopped";
status.ForeColor = System.Drawing.Color.Red;
start.Text = "Start Generating";
}
}
}
private void core()
{
// generate audio
int i = 0;
for (int n = 1; n < co; n++)
{
// generate video and upload to
// youtube, this generates, but
// when uploading to youtube this for
// loop carries on when I want it to
// upload to youtube first before carrying on
generatevideo(image, articlename);
}
}
private void generateVideo(string images, String articlename)
{
//generate the video here, once done upload
{code removed, this just generates a video, nothing important}
// now upload (but I want it to finish before repeating the core() function
try
{
new UploadVideo().Run(articlename, file);
}
catch (AggregateException ex)
{
foreach (var e in ex.InnerExceptions)
{
ThreadSafe(() =>
{
this.Invoke((MethodInvoker)delegate
{
status.Text = e.Message;
status.ForeColor = System.Drawing.Color.Red;
});
});
}
}
}
How I am uploading to Youtube:
using System;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
using Google.Apis.Upload;
using Google.Apis.Util.Store;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
namespace articletoyoutube
{
/// <summary>
/// YouTube Data API v3 sample: upload a video.
/// Relies on the Google APIs Client Library for .NET, v1.7.0 or higher.
/// See https://code.google.com/p/google-api-dotnet-client/wiki/GettingStarted
/// </summary>
class UploadVideo
{
// to access form controlls
Form1 core = new Form1();
public async Task Run(string articlename, string filelocation)
{
UserCredential credential;
using (var stream = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
// This OAuth 2.0 access scope allows an application to upload files to the
// authenticated user's YouTube channel, but doesn't allow other types of access.
new[] {
YouTubeService.Scope.YoutubeUpload
},
"user",
CancellationToken.None
);
}
var youtubeService = new YouTubeService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = Assembly.GetExecutingAssembly().GetName().Name
});
var video = new Video();
video.Snippet = new VideoSnippet();
video.Snippet.Title = articlename;
video.Snippet.Description = "News story regarding" + articlename;
video.Snippet.Tags = new string[] {
"news",
"breaking",
"important"
};
video.Snippet.CategoryId = "25"; // See https://developers.google.com/youtube/v3/docs/videoCategories/list
video.Status = new VideoStatus();
video.Status.PrivacyStatus = "public"; // or "private" or "public"
var filePath = filelocation; // Replace with path to actual movie file.
using (var fileStream = new FileStream(filePath, FileMode.Open))
{
var videosInsertRequest = youtubeService.Videos.Insert(video, "snippet,status", fileStream, "video/*");
videosInsertRequest.ProgressChanged += videosInsertRequest_ProgressChanged;
videosInsertRequest.ResponseReceived += videosInsertRequest_ResponseReceived;
await videosInsertRequest.UploadAsync();
}
}
void videosInsertRequest_ProgressChanged(Google.Apis.Upload.IUploadProgress progress)
{
switch (progress.Status)
{
case UploadStatus.Uploading:
core.prog_up.Text = "{0} bytes sent." + progress.BytesSent;
break;
case UploadStatus.Failed:
core.status.Text = "An error prevented the upload from completing.\n{0}" + progress.Exception;
core.status.ForeColor = System.Drawing.Color.Red;
break;
}
}
void videosInsertRequest_ResponseReceived(Video video)
{
core.prog_up.Text = "Video id '{0}' was successfully uploaded." + video.Id;
}
}
}
The background worker just runs core();
When it reaches the function
new UploadVideo().Run(articlename, file);
It starts uploading but starts repeating the core function again thus generating another video before that video has uploaded.... If I use
new UploadVideo().Run(articlename, file).Wait();
Then the program just stops and waits indefintly until I close the program, how can I wait for the Upload class/method to finish before carrying on with the fore loop in the core method?
To the guy who answered, when I add await before the new Upload... it gives me:
Severity Code Description Project File Line Suppression State
Error CS4033 The 'await' operator can only be used within an async
method. Consider marking this method with the 'async' modifier and
changing its return type to
'Task'. articletoyoutube C:\Users\Laptop\Documents\Visual Studio
2017\Projects\articletoyoutube\articletoyoutube\Form1.cs 254 Active
Make sure the async keyword is used on your methods and use the await keyword for the Tasks.
For example:
private async Task core()
{
// generate audio
int i = 0;
for (int n = 1; n < co; n++)
{
await generatevideo(image, articlename);
}
}
private async Task generateVideo(string images, String articlename)
{
//generate the video here,
try
{
var uploadVideo = new UploadVideo();
await uploadVideo.Run(articlename, file);
}
catch (AggregateException ex)
{
foreach (var e in ex.InnerExceptions)
{
ThreadSafe(() =>
{
this.Invoke((MethodInvoker)delegate
{
status.Text = e.Message;
status.ForeColor = System.Drawing.Color.Red;
});
});
}
}
}
You need to use await all the way up your call stack to where your event handler is, this will require changing many of your methods.
private async Task core()
{
// generate audio
int i = 0;
for (int n = 1; n < co; n++)
{
// generate video and upload to
// youtube, this generates, but
// when uploading to youtube this for
// loop carries on when I want it to
// upload to youtube first before carrying on
await generatevideo(image, articlename);
}
}
private async Task generateVideo(string images, String articlename)
{
//generate the video here, once done upload
{code removed, this just generates a video, nothing important}
// now upload (but I want it to finish before repeating the core() function
try
{
await new UploadVideo().Run(articlename, file);
}
catch (AggregateException ex)
{
foreach (var e in ex.InnerExceptions)
{
ThreadSafe(() =>
{
this.Invoke((MethodInvoker)delegate
{
status.Text = e.Message;
status.ForeColor = System.Drawing.Color.Red;
});
});
}
}
}
Note, using async/await does not work with BackgroundWorker you will need to switch to using Task.Run and a CancellationToken to signal cancellation.
Task _backgroundWork;
CancellationTokenSource _cts;
private void button2_Click(object sender, EventArgs e)
{
if (status.Text == "Stopped")
{
if (!generatearticle.IsBusy)
{
// started
status.Text = "Started";
status.ForeColor = System.Drawing.Color.Green;
start.Text = "Stop Generating";
_cts = new CancellationTokenSource();
_backgroundWork = Task.Run(() => core(_cts.Token), _cts.Token);
}
}
else
{
if(!_backgroundWork.IsCompleted)
{
_cts.Cancel();
// started
status.Text = "Stopped";
status.ForeColor = System.Drawing.Color.Red;
start.Text = "Start Generating";
}
}
}

ProgressBar progress change with all files that need to be downloaded

I'm making a WPF application where I use WebClient to download files form a webserver. I have a list of URL's with all the files i have to download. I use a foreach to loop through every URL and download each one at the time. The first URL much be completed before moving to the next one. I know the size of each file. Is there a way where I can set my e.ProgressPercentage to know the size of all files instead of loading from 0 to 100% for each file. I know that I'm calling DownloadProtocol for each URL right now, which makes a new instance of WebClient, but it is the only way I can think of to fulfill my solution, which is to download one file at a time.
public DownloadStart()
{
foreach(var url in ListOfDownloadURL)
{
DownloadGameFile dlg = new DownloadGameFile();
await dlg.DownloadProtocol(url, myLocation);
}
}
Download function in DownloadGameFile class:
public async Task DownloadProtocol(string address, string location)
{
Uri Uri = new Uri(address);
using (WebClient client = new WebClient())
{
//client.DownloadFileCompleted += new AsyncCompletedEventHandler(Completed);
//client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(DownloadProgress);
client.DownloadProgressChanged += (o, e) =>
{
Console.WriteLine(e.BytesReceived + " " + e.ProgressPercentage);
//ProgressBar = e.ProgressPercentage (total)
};
client.DownloadFileCompleted += (o, e) =>
{
if (e.Cancelled == true)
{
Console.WriteLine("Download has been canceled.");
}
else
{
Console.WriteLine("Download completed!");
}
};
await client.DownloadFileTaskAsync(Uri, location);
}
}
Why not take the easy way out and just update the progress when file is completed? Something like...
ProgressBar p = new ProgressBar();
p.Maximum = ListOfDownloadURL.Count();
foreach(var url in ListOfDownloadURL)
{
DownloadGameFile dlg = new DownloadGameFile();
await dlg.DownloadProtocol(url, myLocation);
p.Value += 1;
}
Or if you insist, you could query file sizes before you begin downloading, sum total bytes of all the files and then calculate the percentage when ever DownloadProgressChanged is fired.
var bytes = Convert.ToInt64(client.ResponseHeaders["Content-Length"]);

C# Download Speed Asynchronously

I'm trying to get the current user's network download speed. After hitting a dead end with NetworkInterfaces and all I tried a solution I found online. I edited it a bit and it works great but it's not asynchronous.
public static void GetDownloadSpeed(this Label lbl)
{
double[] speeds = new double[5];
for (int i = 0; i < 5; i++)
{
int fileSize = 407; //Size of File in KB.
WebClient client = new WebClient();
DateTime startTime = DateTime.Now;
if (!Directory.Exists($"{CurrentDir}/tmp/speedtest"))
Directory.CreateDirectory($"{CurrentDir}/tmp/speedtest");
client.DownloadFile(new Uri("https://ajax.googleapis.com/ajax/libs/threejs/r69/three.min.js"), "/tmp/speedtest/three.min.js");
DateTime endTime = DateTime.Now;
speeds[i] = Math.Round((fileSize / (endTime - startTime).TotalSeconds));
}
lbl.Text = string.Format("{0}KB/s", speeds.Average());
}
That function is called within a timer at an interval of 2 minutes.
MyLbl.GetDownloadSpeed()
I've tried using WebClient.DownloadFileAsync but that just shows the unlimited symbol.My next try would be to use HttpClient but before I go on does anyone have a recommended way of getting the current users download speed asynchronously (without lagging the main GUI thread)?
As it was suggested you could make an async version of GetDownloadSpeed():
async void GetDownloadSpeedAsync(this Label lbl, Uri address, int numberOfTests)
{
string directoryName = #"C:\Work\Test\speedTest";
string fileName = "tmp.dat";
if (!Directory.Exists(directoryName))
Directory.CreateDirectory(directoryName);
Stopwatch timer = new Stopwatch();
timer.Start();
for (int i = 0; i < numberOfTests; ++i)
{
using (WebClient client = new WebClient())
{
await client.DownloadFileTaskAsync(address, Path.Combine(directoryName, fileName), CancellationToken.None);
}
}
lbl.Text == Convert.ToString(timer.Elapsed.TotalSeconds / numberOfTests);
}
WebClient class being relatively old does not have awaitable DownloadFileAsync().
EDITED
As it was correctly pointed out WebClient in fact has a task-based async method DownloadFileTaskAsync(), which i advise to use. The code below can still help addressing the case when async method returning Task is not provided.
We can fix it with the help of TaskCompletionSource<T>:
public static class WebClientExtensions
{
public static Task DownloadFileAwaitableAsync(this WebClient instance, Uri address,
string fileName, CancellationToken cancellationToken)
{
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
// Subscribe for completion event
instance.DownloadFileCompleted += instance_DownloadFileCompleted;
// Setup cancellation
var cancellationRegistration = cancellationToken.CanBeCanceled ? (IDisposable)cancellationToken.Register(() => { instance.CancelAsync(); }) : null;
// Initiate asyncronous download
instance.DownloadFileAsync(address, fileName, Tuple.Create(tcs, cancellationRegistration));
return tcs.Task;
}
static void instance_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
((WebClient)sender).DownloadDataCompleted -= instance_DownloadFileCompleted;
var data = (Tuple<TaskCompletionSource<object>, IDisposable>)e.UserState;
if (data.Item2 != null) data.Item2.Dispose();
var tcs = data.Item1;
if (e.Cancelled)
{
tcs.TrySetCanceled();
}
else if (e.Error != null)
{
tcs.TrySetException(e.Error);
}
else
{
tcs.TrySetResult(null);
}
}
}
Try `await Task.Run(()=> { //your code });
Edit: #JustDevInc I still think you should use DownloadAsync. Task.Run(delegate) creates a new thread and you might want to avoid that. If you want, post some of your old code so we can try to fix it.
Edit: The first solution turned out to be the only one of the two working. DownloadFileAsync doesn't return task, so can't it awaited.

Categories

Resources