C# UI for AWS S3 not updating - c#

I am creating a download application in C# that downloads files from Amazon AWS S3 storage. I am able to download the file without issue, but I am trying to create a progress event.
To create the event I am using the following code within the download function:
Application.DoEvents();
response2.WriteObjectProgressEvent += displayProgress;
Application.DoEvents();
The event handler I created is as follows:
private void displayProgress(object sender, WriteObjectProgressArgs args)
{
// Counter for Event runs
label7.BeginInvoke(new Action(() => label7.Text = (Convert.ToInt32(label7.Text)+1).ToString()));
Application.DoEvents();
// transferred bytes
label4.BeginInvoke(new Action(() => label4.Text = args.TransferredBytes.ToString()));
Application.DoEvents();
// progress bar
progressBar1.BeginInvoke(new Action(() => progressBar1.Value = args.PercentDone));
Application.DoEvents();
}
The issue is that it only updates when a file it downloaded, but the event runs more often. When I download the last file (12MB); lable7 (event counter) jumps from 3 to 121, so I know it is running, but just not updating.
I have also tried just a 'standard' Invoke, but I had the same result.
Additional Code of the function:
AmazonS3Config S3Config = new AmazonS3Config
{
ServiceURL = "https://s3.amazonaws.com"
};
var s3Client = new AmazonS3Client(stuff, stuff, S3Config);
ListBucketsResponse response = s3Client.ListBuckets();
GetObjectRequest request = new GetObjectRequest();
request.BucketName = "dr-test";
request.Key = locationoffile[currentdownload];
GetObjectResponse response2 = s3Client.GetObject(request);
response2.WriteObjectProgressEvent += displayProgress;
string pathlocation = Path.GetDirectoryName(Directory.GetCurrentDirectory()) + "\\" + Instrument[currentdownload] + "\\" + NewFileName[currentdownload];
response2.WriteResponseStreamToFile(pathlocation);

You're not using the asynchronous call for GetObject or WriteResponseStreamToFile, so the UI thread (that you're calling it from) is going to be blocked, which means that it can't update the progress (regardless of those DoEvents calls, which are generally considered evil and you should avoid).
Without actually being able to try it myself, here's what I think you need to do.
private async void Button_Click(object sender, EventArgs e)
{
foreach(...download....in files to download){
AmazonS3Config S3Config = new AmazonS3Config
{
ServiceURL = "https://s3.amazonaws.com"
};
var s3Client = new AmazonS3Client(stuff, stuff, S3Config);
ListBucketsResponse response = s3Client.ListBuckets();
GetObjectRequest request = new GetObjectRequest();
request.BucketName = "dr-test";
request.Key = locationoffile[currentdownload];
GetObjectResponse response2 = await s3Client.GetObjectAsync(request, null);
response2.WriteObjectProgressEvent += displayProgress;
string pathlocation = Path.GetDirectoryName(Directory.GetCurrentDirectory()) + "\\" + Instrument[currentdownload] + "\\" + NewFileName[currentdownload];
await response2.WriteResponseStreamToFileAsync(pathlocation, null);
}
}
The two nulls that I added are for cancellation tokens, I can't tell from the AWS docs if it's allowed to pass a null token, if not please create one and pass it in.

Related

How to run the UI Grafik and reader function without letting the UI get stopped until function ends? [duplicate]

I have a button that after I click it send a lot of data in a remote database with a loop, but during this operation whole wpf UI is freezing. My goal is to make the loader work while it is processing everything with the database.
My button code:
private void btn_Start_Click(object sender, RoutedEventArgs e)
{
pb_loader.IsIndeterminate = true; //<- it has to start to make animation
IEmailService emailService = new EmailService();
IUserQueryService emailQueryService = new UserQueryService();
var idIniziale = int.Parse(txtIdIniziale.Text);
var idFinale = int.Parse(txtIdFinale.Text);
var n = idFinale - idIniziale;
string mail = "";
for(int i=0; i<=n; i++)
{
mail = txtMail.Text + idIniziale + "#mail.local";
var exist = emailQueryService.CheckUserExist(mail); //<- db operation method
if (exist == false)
{
var lastUniqueId = emailQueryService.GetLastUniqueId();//<- db operation method
lastUniqueId = lastUniqueId + 1;
var idUtente = emailService.SalvaUtente(mail, lastUniqueId); //<- db operation method
emailService.AssegnaReferente(idUtente, txtMail.Text);//<- db operation method
emailService.AssegnaRuoli(idUtente); //<- db operation method
}
idIniziale++;
}
pb_loader.IsIndeterminate = false; //<- it has to end animation of loading
}
One straighforward approach for running a background operation in an event handler is to declare the event handler async and run and await a Task:
private async void btn_Start_Click(object sender, RoutedEventArgs e)
{
// prevent click while operation in progress
btn_Start.IsEnabled = false;
pb_loader.IsIndeterminate = true;
// access UI elements before running the Task
var mail = txtMail.Text + idIniziale + "#mail.local";
...
await Task.Run(() =>
{
// perform background operation
// use local variables "mail" etc. here
});
pb_loader.IsIndeterminate = false;
btn_Start.IsEnabled = true;
}

Asynchronous Google File Upload with Progress Bar WPF

I am uploading to Google Cloud Storage using a service account. I need to be able to display the progress of the upload in the WPF UI. Now, Whenever I try to update the ProgressBar.Value, it's not working, but when I just have the bytesSent written in Console, I can see the progress.
public async Task<bool> UploadToGoogleCloudStorage(string bucketName, string token, string filePath, string contentType)
{
var newObject = new Google.Apis.Storage.v1.Data.Object()
{
Bucket = bucketName,
Name = System.IO.Path.GetFileNameWithoutExtension(filePath)
};
var service = new Google.Apis.Storage.v1.StorageService();
try
{
using (var fileStream = new FileStream(filePath, FileMode.Open))
{
var uploadRequest = new ObjectsResource.InsertMediaUpload(service, newObject, bucketName, fileStream, contentType);
uploadRequest.OauthToken = token;
ProgressBar.Maximum = fileStream.Length;
uploadRequest.ProgressChanged += UploadProgress;
uploadRequest.ChunkSize = (256 * 1024);
await uploadRequest.UploadAsync().ConfigureAwait(false);
service.Dispose();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw ex;
}
return true;
}
private void UploadProgress(IUploadProgress progress)
{
switch (progress.Status)
{
case UploadStatus.Starting:
ProgressBar.Minimum = 0;
ProgressBar.Value = 0;
break;
case UploadStatus.Completed:
System.Windows.MessageBox.Show("Upload completed!");
break;
case UploadStatus.Uploading:
//Console.WriteLine(progress.BytesSent); -> This is working if I don't call the method below.
UpdateProgressBar(progress.BytesSent);
break;
case UploadStatus.Failed:
Console.WriteLine("Upload failed "
+ Environment.NewLine
+ progress.Exception.Message
+ Environment.NewLine
+ progress.Exception.StackTrace
+ Environment.NewLine
+ progress.Exception.Source
+ Environment.NewLine
+ progress.Exception.InnerException
+ Environment.NewLine
+ "HR-Result" + progress.Exception.HResult);
break;
}
}
private void UpdateProgressBar(long value)
{
Dispatcher.Invoke(() => { this.ProgressBar.Value = value; });
}
I re-created as much of your project as I could, and the progress bar updates correctly as the image uploads. Given your callback still works when writing to the console it's possible there's just an issue with how you're using the ProgressBar in the UI itself.
Read on to see the details of what I did to get it working.
This is for Google.Cloud.Storage.V1 (not Google.Apis.Storage.v1), but appears to be a bit simpler to perform an upload now. I started with the Client libraries "Getting Started" instructions to create a service account and bucket, then experimented to find out how to upload an image.
The process I followed:
Sign up for Google Cloud free trial
Create a new project in Google Cloud (remember the project name\ID for later)
Create a Project Owner service account - this will result in a json file being downloaded that contains the service account credentials. Remember where you put that file.
The getting started docs get you to add the path to the JSON credentials file into an environment variable called GOOGLE_APPLICATION_CREDENTIALS - I couldn't get this to work through the provided instructions. Turns out it is not required, as you can just read the JSON file into a string and pass it to the client constructor.
I created an empty WPF project as a starting point, and a single ViewModel to house the application logic.
Install the Google.Cloud.Storage.V1 nuget package and it should pull in all the dependencies it needs.
Onto the code.
MainWindow.xaml
<StackPanel>
<Button
Margin="50"
Height="50"
Content="BEGIN UPLOAD"
Click="OnButtonClick" />
<ContentControl
Content="{Binding Path=ProgressBar}" />
</StackPanel>
MainWindow.xaml.cs
public partial class MainWindow
{
readonly ViewModel _viewModel;
public MainWindow()
{
_viewModel = new ViewModel(Dispatcher);
DataContext = _viewModel;
InitializeComponent();
}
void OnButtonClick(object sender, RoutedEventArgs args)
{
_viewModel.UploadAsync().ConfigureAwait(false);
}
}
ViewModel.cs
public class ViewModel
{
readonly Dispatcher _dispatcher;
public ViewModel(Dispatcher dispatcher)
{
_dispatcher = dispatcher;
ProgressBar = new ProgressBar {Height=30};
}
public async Task UploadAsync()
{
// Google Cloud Platform project ID.
const string projectId = "project-id-goes-here";
// The name for the new bucket.
const string bucketName = projectId + "-test-bucket";
// Path to the file to upload
const string filePath = #"C:\path\to\image.jpg";
var newObject = new Google.Apis.Storage.v1.Data.Object
{
Bucket = bucketName,
Name = System.IO.Path.GetFileNameWithoutExtension(filePath),
ContentType = "image/jpeg"
};
// read the JSON credential file saved when you created the service account
var credential = Google.Apis.Auth.OAuth2.GoogleCredential.FromJson(System.IO.File.ReadAllText(
#"c:\path\to\service-account-credentials.json"));
// Instantiates a client.
using (var storageClient = Google.Cloud.Storage.V1.StorageClient.Create(credential))
{
try
{
// Creates the new bucket. Only required the first time.
// You can also create buckets through the GCP cloud console web interface
storageClient.CreateBucket(projectId, bucketName);
System.Windows.MessageBox.Show($"Bucket {bucketName} created.");
// Open the image file filestream
using (var fileStream = new System.IO.FileStream(filePath, System.IO.FileMode.Open))
{
ProgressBar.Maximum = fileStream.Length;
// set minimum chunksize just to see progress updating
var uploadObjectOptions = new Google.Cloud.Storage.V1.UploadObjectOptions
{
ChunkSize = Google.Cloud.Storage.V1.UploadObjectOptions.MinimumChunkSize
};
// Hook up the progress callback
var progressReporter = new Progress<Google.Apis.Upload.IUploadProgress>(OnUploadProgress);
await storageClient.UploadObjectAsync(
newObject,
fileStream,
uploadObjectOptions,
progress: progressReporter)
.ConfigureAwait(false);
}
}
catch (Google.GoogleApiException e)
when (e.Error.Code == 409)
{
// When creating the bucket - The bucket already exists. That's fine.
System.Windows.MessageBox.Show(e.Error.Message);
}
catch (Exception e)
{
// other exception
System.Windows.MessageBox.Show(e.Message);
}
}
}
// Called when progress updates
void OnUploadProgress(Google.Apis.Upload.IUploadProgress progress)
{
switch (progress.Status)
{
case Google.Apis.Upload.UploadStatus.Starting:
ProgressBar.Minimum = 0;
ProgressBar.Value = 0;
break;
case Google.Apis.Upload.UploadStatus.Completed:
ProgressBar.Value = ProgressBar.Maximum;
System.Windows.MessageBox.Show("Upload completed");
break;
case Google.Apis.Upload.UploadStatus.Uploading:
UpdateProgressBar(progress.BytesSent);
break;
case Google.Apis.Upload.UploadStatus.Failed:
System.Windows.MessageBox.Show("Upload failed"
+ Environment.NewLine
+ progress.Exception);
break;
}
}
void UpdateProgressBar(long value)
{
_dispatcher.Invoke(() => { ProgressBar.Value = value; });
}
// probably better to expose progress value directly and bind to
// a ProgressBar in the XAML
public ProgressBar ProgressBar { get; }
}

How to change a control property during main thread execution?

I know there are several question related to my issues, I've studied them all but it seems that I still can't get it.
Something like this or like this.
I have a method that downloads some files through FTP (it takes somewhere around 5 seconds). When I click the button to download the files I also want to change a control property so that a "loading" kind of thing is visible.
For this I have a CircleProgressBar with "animated" property set by default to false. When I call the previous method I want first to change that property to true and after the download is complete to set it back to false as it was.
I tried many solutions but in vain:
void UpdateMessage(bool value)
{
Action action = () => DownloadLC_Normal_CircleProgressBar.animated = value;
Invoke(action);
}
private void DownloadLC_Normal_Button_Click(object sender, EventArgs e)
{
// try 1
//UpdateMessage(true);
// try 2
//DownloadLC_Normal_CircleProgressBar.Invoke((MethodInvoker)(() =>
//{
// DownloadLC_Normal_CircleProgressBar.animated = true;
//}));
// try 3
if (DownloadLC_Normal_CircleProgressBar.InvokeRequired)
{
DownloadLC_Normal_CircleProgressBar.BeginInvoke((MethodInvoker)delegate () { DownloadLC_Normal_CircleProgressBar.animated = true; });
}
else
{
DownloadLC_Normal_CircleProgressBar.animated = false;
}
// DOWNLOAD FILES THROUGH FTP BY CALLING A METHOD FROM A .cs FILE
// FROM THE PROJECT
//UpdateMessage(false);
//DownloadLC_Normal_CircleProgressBar.animated = false;
}
The CircleProgressBar never animates. What am I missing? What am I doing wrong, please? :(
EDIT:
My missing part of code:
ftp ftpClient = new ftp("ftp://" + "192.168.1.200" + "/", "anonymous", "anonymous");
NetworkCredential credentials = new NetworkCredential("anonymous", "anonymous");
string url = "ftp://" + "192.168.1.200" + "/Documents and Settings/";
ftpClient.DownloadFtpDirectory(url, credentials, newDirectoryDownloadLocation);
I'm assuming you're using framework 4.5/higher or 4.0 with Microsoft.Bcl.Async installed ok.
Try it:
private async void DownloadLC_Normal_Button_Click(object sender, EventArgs e)
{
try
{
DownloadLC_Normal_Button.Enabled = false;
DownloadLC_Normal_CircleProgressBar.animated = true;
ftp ftpClient = new ftp("ftp://" + "192.168.1.200" + "/", "anonymous", "anonymous");
NetworkCredential credentials = new NetworkCredential("anonymous", "anonymous");
string url = "ftp://" + "192.168.1.200" + "/Documents and Settings/";
//the code you post + change this line from:
//ftpClient.DownloadFtpDirectory(url, credentials, newDirectoryDownloadLocation);
//to: It makes the call be async
await Task.Run(() => ftpClient.DownloadFtpDirectory(url, credentials, newDirectoryDownloadLocation));
}
finally
{
DownloadLC_Normal_CircleProgressBar.animated = false;
DownloadLC_Normal_Button.Enabled = true;
}
}
One of the easiest options is to use async/await:
async void DownloadLC_Normal_Button_Click(object sender, EventArgs e)
{
DownloadLC_Normal_CircleProgressBar.animated = true;
DownloadLC_Normal_Button.Enabled = false; // prevent further clicks
await Task.Run(() =>
{
... // long running code, use `Invoke` to update UI controls
});
DownloadLC_Normal_CircleProgressBar.animated = false;
DownloadLC_Normal_Button.Enabled = true;
}

C# WinForm progress bar not progressively increasing

So I've made a simple Mp3 to Wave converter using NAudio. Everything works fine, except that there is a bug that I really don't like. Here's the section of the code that does the conversion:
foreach (mp3file file in fileList){
string outputfilename = fbd.SelectedPath + "\\" + file.name + ".wav";
using (Mp3FileReader reader = new Mp3FileReader(file.path)){
using (WaveStream convertedStream = WaveFormatConversionStream.CreatePcmStream(reader)){
WaveFileWriter.CreateWaveFile(outputfilename, convertedStream);
}
}
progressBar.PerformStep(); //This isn't working.
}
I wanted the program to make the progressbar perform a step each time a song gets converted, but instead the progress bar stays empty for the entire conversion process and then increases all at once when the conversion is finished.
It's not that worrying tho, if there's not simple solution I'll bear this bug.
You should be using either BackgroundWorker or async and await to perform for progress bar updates. The loops generally block the WinForms and it looks like it is freezed and nothing is happening. Whereas the BackgroundWorker reports the UI thread if any changes has been made.
In the constructor
{
backgroundWorker.RunWorkerAsync();
}
BackgroundWorker implementation
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
var backgroundWorker = sender as BackgroundWorker;
foreach (mp3file file in fileList)
{
string outputfilename = fbd.SelectedPath + "\\" + file.name + ".wav";
using (Mp3FileReader reader = new Mp3FileReader(file.path))
{
using (WaveStream convertedStream = WaveFormatConversionStream.CreatePcmStream(reader)){
WaveFileWriter.CreateWaveFile(outputfilename, convertedStream);
}
backgroundWorker.ReportProgress();
}
}
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Do something When the loop or operation is completed.
}
Your Conversion and progressbar updation is taking place on same thread so updating the GUI components do not take place until the conversion is completed just use Application.doEvents() as it will process all pending messages in que of application after progressbar.PerformStep() then it will not fill up at once at the end of processing but keeps on updating with the processing
foreach (mp3file file in fileList){
string outputfilename = fbd.SelectedPath + "\\" + file.name + ".wav";
using (Mp3FileReader reader = new Mp3FileReader(file.path)){
using (WaveStream convertedStream = WaveFormatConversionStream.CreatePcmStream(reader)){
WaveFileWriter.CreateWaveFile(outputfilename, convertedStream);
}
}
progressBar.PerformStep(); //This isn't working alone
Application.DoEvents(); //This is working fine now
}
Assuming that this is all taking place on the UI thread, try calling the Refresh() method on the progress bar object, to force it to redraw.
foreach (mp3file file in fileList){
string outputfilename = fbd.SelectedPath + "\\" + file.name + ".wav";
using (Mp3FileReader reader = new Mp3FileReader(file.path)){
using (WaveStream convertedStream = WaveFormatConversionStream.CreatePcmStream(reader)){
WaveFileWriter.CreateWaveFile(outputfilename, convertedStream);
}
progressBar.PerformStep(); // This isn't working.
progressBar.Refresh(); // This might fix it
}
But it's better to offload this sort of work to a BackgroundWorker.

Live Tiles not Updating

I'm trying to create a background agent that periodically updates a user's Live Tiles on Windows Phone.
Currently, my code for the agent is:
string where = "";
private GeoCoordinate MyCoordinate = null;
HttpWebResponse webResponse;
...
protected override void OnInvoke(ScheduledTask task)
{
System.Diagnostics.Debug.WriteLine("Invoked");
findMe();
NotifyComplete();
}
private void ResponseCallback(IAsyncResult asyncResult)
{
HttpWebRequest webRequest = (HttpWebRequest)asyncResult.AsyncState;
webResponse = (HttpWebResponse)webRequest.EndGetResponse(asyncResult);
MemoryStream tempStream = new MemoryStream();
webResponse.GetResponseStream().CopyTo(tempStream);
}
private async void findMe()
{
Geolocator geolocator = new Geolocator();
geolocator.DesiredAccuracy = PositionAccuracy.High;
try
{
Geoposition currentPosition = await geolocator.GetGeopositionAsync(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10));
MyCoordinate = new GeoCoordinate(currentPosition.Coordinate.Latitude, currentPosition.Coordinate.Longitude);
// var uri = new Uri("http://www.streetdirectory.com//api/?mode=nearby&act=location&output=json&callback=foo&start=0&limit=1&country=sg&profile=template_1&x=" + MyCoordinate.Longitude + "&y=" + MyCoordinate.Latitude + "&dist=1");
// var client = new HttpClient();
var webRequest = (HttpWebRequest)HttpWebRequest.CreateHttp("http://www.streetdirectory.com//api/?mode=nearby&act=location&output=json&callback=foo&start=0&limit=1&country=sg&profile=template_1&x=" + MyCoordinate.Longitude + "&y=" + MyCoordinate.Latitude + "&dist=1");
webRequest.BeginGetResponse(new AsyncCallback(ResponseCallback), webRequest);
System.Diagnostics.Debug.WriteLine("findMe after response");
System.Diagnostics.Debug.WriteLine(MyCoordinate.Latitude);
System.Diagnostics.Debug.WriteLine(MyCoordinate.Longitude);
// var response = await client.GetStringAsync(uri);
System.Diagnostics.Debug.WriteLine(webResponse.ToString());
JToken token = JArray.Parse(webResponse.ToString())[0];
// JToken token = JArray.Parse(response)[0];
var name = token.Next.First.First;
var address = token.Next.Last.First;
where = name + ", " + address;
}
catch (Exception)
{
System.Diagnostics.Debug.WriteLine("findMe died");
where = "";
}
System.Diagnostics.Debug.WriteLine("findMe complete");
UpdateAppTile();
}
private void UpdateAppTile()
{
System.Diagnostics.Debug.WriteLine("UpdateAppTile");
ShellTile appTile = ShellTile.ActiveTiles.First();
if (appTile != null)
{
StandardTileData tileData = new StandardTileData
{
BackContent = where
};
appTile.Update(tileData);
}
System.Diagnostics.Debug.WriteLine("Update Completed: " + where);
}
When I attempt to run this, the code reaches webRequest.BeginGetResponse and subsequently stops. The next line, and ResponseCallback are not reached.
An older version of my code is commented out, which I thought was the problem but it experienced the same problem as well.
The problem is that you are calling NotifyComplete() before the callback returns.
By calling NotifyComplete you're telling the OS that you've finished all your work and the agent can be terminated. Obviously this isn't the case when you're waiting for your webrequest callback.
The simple solution is to move this call into the callback method. obviously you'll need to handle error exceptions and the request timeout taking longer than the agent will wait for as well though.
Changing to using awaitable code may make this easier for you.

Categories