I am currently having issies with the await / asyn methods, i'm trying to import a .txt file of URLs but the UI is still locking up.
Code
public async void DoImport(string[] files)
{
ListViewItem lv = null;
foreach (string file in files)
{
lv = new ListViewItem(file);
listViewSites.Items.Add(lv);
lv.SubItems.Add("...");
lv.SubItems.Add("...");
}
}
public async Task ImportTheSitesToUse(string inputFileIncludingFullPath)
{
try
{
string[] files = File.ReadAllLines(inputFileIncludingFullPath);
await Task.Run(() => DoImport(files));
}
catch (Exception ex)
{
Helpers.DebugLogging("[" + DateTime.Now + "]-[" + ex.ToString() + "]");
}
}
private async void BtnImportSitesList_Click(object sender, EventArgs e)
{
try
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Title = "Select your .txt list of URLs ...";
ofd.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*";
BtnImportSitesList.Enabled = false;
if (ofd.ShowDialog() == DialogResult.OK)
{
await ImportTheSitesToUse(ofd.FileName.ToString());
}
BtnImportSitesList.Enabled = true;
Helpers.ReturnMessage("Successfully imported [ x ] links.");
}
catch (Exception ex)
{
Helpers.DebugLogging("[" + DateTime.Now + "]-[" + ex.ToString() + "]");
}
}
I don't see any obvious issues with the code, any help would be appreciated.
There are a couple of common issues in the code:
public async void DoImport(string[] files)
This method is marked as async, but it doesn't use await, which makes the method run synchronously. If you look at the Errors window, you will see a warning that complains about that method. Make it public void, that's enough.
Let's break down ImportTheSitesToUse now. It basically does two things:
Read all lines in a file, and
Requests a thread pool thread to call DoImport
If we keep in mind that with async/await, the control is returned to the caller on an await call, the issue becomes obvious when the biggest part of the work is executed synchronously.
The second issue is that there is no reason to call DoImport from a thread pool thread, since it is an inexpensive method, and it also requires to be run on the UI thread (due to the UI object that is being altered).
That said, ImportTheSitesToUse should then have the opposite structure:
Read all lines in a file asynchronously, and
Call DoImport
Putting it all together:
public void DoImport(string[] files)
{
foreach (string file in files)
{
ListViewItem lv = new ListViewItem(file);
listViewSites.Items.Add(lv);
lv.SubItems.Add("...");
lv.SubItems.Add("...");
}
}
public async Task ImportTheSitesToUse(string inputFileIncludingFullPath)
{
try
{
string[] files = await Task.Run(() => File.ReadAllLines(inputFileIncludingFullPath));
DoImport(files);
}
catch (Exception ex)
{
Helpers.DebugLogging("[" + DateTime.Now + "]-[" + ex.ToString() + "]");
}
}
Related
I want to build a folder cleaner program. It is expected to report deleted files to a TextBox control at real-time. So I use await Task.Run(() => CleanFolder(folderPath, progress)) function in my button click event. But the UI blocked when running. After a while when the CheanFolder() method run complete, all the deleted files are showed at one time.
namespace FolderCleaner
{
public partial class MainWindow : Window
{
string folderPath;
string matchPattern;
private void ButtonOpen_Click(object sender, RoutedEventArgs e)
{
FolderBrowserDialog fbd = new FolderBrowserDialog() { Description = "Select a folder" };
if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
folderPath = fbd.SelectedPath;
textBoxPath.Text = folderPath;
buttonClean.IsEnabled = true;
textBoxList.Text = "Folder path: " + folderPath + "\n";
}
}
private async void ButtonClean_Click(object sender, RoutedEventArgs e)
{
matchPattern = textBoxPattern.Text;
buttonOpen.IsEnabled = false;
buttonClean.IsEnabled = false;
Progress<string> progress = new Progress<string>(msg =>
{
textBoxList.AppendText("File deleted: " + msg + "\n");
textBoxList.CaretIndex = textBoxList.Text.Length;
textBoxList.ScrollToEnd();
});
try
{
await Task.Run(() => CleanFolder(folderPath, progress));
textBoxList.AppendText("Mission complete!");
textBoxList.CaretIndex = textBoxList.Text.Length;
textBoxList.ScrollToEnd();
}
catch
{
System.Windows.MessageBox.Show("Error!");
}
finally
{
buttonOpen.IsEnabled = true;
}
}
private void CleanFolder(string path, IProgress<string> progress)
{
var filePaths = Directory.EnumerateFiles(path, "*.*", System.IO.SearchOption.AllDirectories);
foreach (var filePath in filePaths)
{
var matchResult = Regex.Match(filePath, matchPattern);
if (matchResult.Success)
{
File.Delete(filePath);
progress.Report(filePath);
}
}
}
}
}
GUI can`t be controlled from another thread.
But i think, that real problem is that concatenating of string and output to a TextBox is a very inefficient operation.
In your case it is better to show progress of removal in a single line or by using the progress bar.
Here is my solution of your problem (i`ve changed 2 methods):
private async void ButtonClean_Click(object sender, RoutedEventArgs e)
{
matchPattern = textBoxPattern.Text;
buttonOpen.IsEnabled = false;
buttonClean.IsEnabled = false;
await Task.Run(() => CleanFolder(folderPath));
textBoxList.Text += "Mission complete!";
buttonOpen.IsEnabled = true;
}
private void CleanFolder(string path)
{
var filePaths = Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories);
foreach (var filePath in filePaths)
{
var matchResult = Regex.Match(filePath, matchPattern);
if (matchResult.Success)
{
File.Delete(filePath);
System.Windows.Application.Current.Dispatcher.Invoke(delegate
{
// this working fast
textBoxList.Text = "File deleted: " + filePath + "\n";
// this working slow and slower over time
//textBoxList.Text += "File deleted: " + filePath + "\n";
textBoxList.ScrollToEnd();
});
}
}
}
I hope this will help.
Thanks for everyone. Thanks to the book C# 6.0 in a nutshell
I have figured out the solution and have a better understanding of async/await.
First of all, Dispatcher.Invoke is not recommended to use since .Net Framework 4.5, task-based asynchrony has become the dominant pattern (using async/awit).
Second, there are a few principles of using async/await:
The expression after await must be a Task or Task<TResult>
object
If you use async modifier to a method, then the method dont
need to return aTaskmethod manually. The compile will wrap the
method as aTask` object.
If you use a method like async Task Foo(), you must use an await keyword in it.
If there is nothing to await, then remove the async modifier, return a Task object by using return Task.Run(() => { Do Something });. Now you can use await Foo() in the method that calling Foo().
Task Foo() can not operate UI, but async Task Foo() can.
This question already has answers here:
Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on
(22 answers)
Closed 10 years ago.
I have the following code that uploads a file to server and updates the progress of the upload in a bar.
private void UploadButton_Click(object sender, EventArgs e)
{
Cursor = Cursors.WaitCursor;
try
{
// get some info about the input file
System.IO.FileInfo fileInfo = new System.IO.FileInfo(FileTextBox.Text);
UploadDocument(fileInfo);
// show start message
LogText("Starting uploading " + fileInfo.Name);
LogText("Size : " + fileInfo.Length);
}
catch (Exception ex)
{
LogText("Exception : " + ex.Message);
if (ex.InnerException != null) LogText("Inner Exception : " + ex.InnerException.Message);
}
finally
{
Cursor = Cursors.Default;
}
}
private async void UploadDocument(System.IO.FileInfo fileInfo)
{
var someTask = await Task.Run<bool>(() =>
{
// open input stream
using (System.IO.FileStream stream = new System.IO.FileStream(FileTextBox.Text, System.IO.FileMode.Open, System.IO.FileAccess.Read))
{
using (StreamWithProgress uploadStreamWithProgress = new StreamWithProgress(stream))
{
uploadStreamWithProgress.ProgressChanged += uploadStreamWithProgress_ProgressChanged;
// start service client
FileTransferWCF.FileTransferServiceClient client = new FileTransferWCF.FileTransferServiceClient();
//FileTransferClient.FileTransferServiceClient client = new FileTransferClient.FileTransferServiceClient();
// upload file
client.UploadFile(fileInfo.Name, fileInfo.Length, uploadStreamWithProgress);
LogText("Done!");
// close service client
client.Close();
}
}
return true;
});
}
void uploadStreamWithProgress_ProgressChanged(object sender, StreamWithProgress.ProgressChangedEventArgs e)
{
if (e.Length != 0)
progressBar1.Value = (int)(e.BytesRead * 100 / e.Length);
}
Im getting the error: "Cross-thread operation not valid: Control 'progressBar1' accessed from a thread other than the thread it was created on." in the line:
progressBar1.Value = (int)(e.BytesRead * 100 / e.Length);
Maybe Im doing this wrong. I'm new to Task Library in .Net.
Any clue?
I recommend reading my async/await intro. One of the guidelines for async programming is to avoid async void; that is, use async Task instead of async void unless you're writing an event handler.
Also, once you start using async, try to use it everywhere. It really simplifies the code.
So, your code could be changed like this (assuming StreamWithProgress uses EAP conventions):
private async void UploadButton_Click(object sender, EventArgs e)
{
UploadButton.Enabled = false;
Cursor = Cursors.WaitCursor;
try
{
// get some info about the input file
System.IO.FileInfo fileInfo = new System.IO.FileInfo(FileTextBox.Text);
var task = UploadDocument(fileInfo);
// show start message
LogText("Starting uploading " + fileInfo.Name);
LogText("Size : " + fileInfo.Length);
await task;
LogText("Done!");
}
catch (Exception ex)
{
LogText("Exception : " + ex.Message);
if (ex.InnerException != null) LogText("Inner Exception : " + ex.InnerException.Message);
}
finally
{
Cursor = Cursors.Default;
UploadButton.Enabled = true;
}
}
private async Task UploadDocument(System.IO.FileInfo fileInfo)
{
// open input stream
using (System.IO.FileStream stream = new System.IO.FileStream(FileTextBox.Text, System.IO.FileMode.Open, System.IO.FileAccess.Read, FileShare.Read, 4096, true))
{
using (StreamWithProgress uploadStreamWithProgress = new StreamWithProgress(stream))
{
uploadStreamWithProgress.ProgressChanged += uploadStreamWithProgress_ProgressChanged;
// start service client
FileTransferWCF.FileTransferServiceClient client = new FileTransferWCF.FileTransferServiceClient();
// upload file
await client.UploadFileAsync(fileInfo.Name, fileInfo.Length, uploadStreamWithProgress);
// close service client
client.Close();
}
}
}
void uploadStreamWithProgress_ProgressChanged(object sender, StreamWithProgress.ProgressChangedEventArgs e)
{
if (e.Length != 0)
progressBar1.Value = (int)(e.BytesRead * 100 / e.Length);
}
You need to make UI updates on the UI thread.
progressBar1.Invoke(new Action(() =>
{ progressBar1.Value = (int)(e.BytesRead * 100 / e.Length); }));
or alternatively (if you don't want to block until the method returns)
progressBar1.BeginInvoke(new Action(() =>
{ progressBar1.Value = (int)(e.BytesRead * 100 / e.Length); }));
Apologies for the syntax, I don't have access to VS at the moment.
I have to load two large files in parallels
so far I have this code
The code below is click button method
private async void MILoadLogFile_Click(object sender, RoutedEventArgs e)
{
...
if (oFD.ShowDialog() == true)
{
await myLogSession.LoadCompassLogAsync(oFD.FileName);
await myLogSession.LoadCoreServiceLogAsync(oFD.FileName);
}
}
loading method:
public async Task LoadCompassLogAsync(String fileName)
{
StreamReader streamReader = new StreamReader(fileName);
if (fileName.Contains("Compass"))
{
...
try
{
using (streamReader)
{
//Console.Out.WriteLine("lineCount: " + lineCount);
while (((line = await streamReader.ReadLineAsync()) != null)
&& !CompassLogLoadCompleted)
{
...
loggingLvl = new LoggingLvl(eLoggingLvl);
CompassLogData cLD = new CompassLogData(id, dateTime, loggingLvl, threadId, loggingMessage);
await addRoCompassLogCollectionAsync(cLD);
}
}
}
catch (Exception e)
{
Console.WriteLine("The file could not be read:");
Console.WriteLine(e.Message);
}
}
}
the LoadCoreServiceLogAsync is almost identical to LoadCompassLogAsync.
The two loading methods runs sequentially. I want them to run in parallel.
Your code will run one task after the other. To run the two tasks in parallel you can use the Task.WaitAll method:
var loadCompassLogTask = myLogSession.LoadCompassLogAsync(oFD.FileName);
var loadCoreServiceLogTask = myLogSession.LoadCoreServiceLogAsync(oFD.FileName);
Task.WaitAll(loadCompassLogTask, loadCoreServiceLogTask);
Or if you want to use await you can use Task.WhenAll:
var loadCompassLogTask = myLogSession.LoadCompassLogAsync(oFD.FileName);
var loadCoreServiceLogTask = myLogSession.LoadCoreServiceLogAsync(oFD.FileName);
await Task.WhenAll(loadCompassLogTask, loadCoreServiceLogTask);
I divided my programme in 3 layers; GUI, BL, IO and tried to grap files from my server to my pc. I made it multi threaded and zorks fine, but when i tried to add a delegate to it for sending messages from my IO to my GUI, it troubels me. It said something like:
It is not allowed to perform an operation through various threads: it
was from another thread had access to the control label download
progress than the thread on which the element is created.
What i have is this:
GUI
private void buttonDownload_Click(object sender, EventArgs e)
{
download = new BL_DataTransfer(Wat.FILM, titel, this.downloadDel);
t = new Thread(new ThreadStart(download.DownLoadFiles));
t.Start();
}
private void UpdateDownloadLabel(string File)
{
labelDownloadProgress.Text = "Downloading: " + File;
}
BL
public void DownLoadFiles()
{
//bestanden zoeken op server
string map = BASEDIR + this.wat.ToString() + #"\" + this.titel + #"\";
string[] files = IO_DataTransfer.GrapFiles(map);
//pad omvormen
string[] downloadFiles = this.VeranderNaarDownLoadPad(files,this.titel);
IO_DataTransfer.DownloadFiles(#".\" + this.titel + #"\", files, downloadFiles, this.obserdelegate);
}
IO
public static void DownloadFiles(string map, string[] bestanden, string[] uploadPlaats, ObserverDelegate observerDelegete)
{
try
{
Directory.CreateDirectory(map);
for (int i = 0; i < bestanden.Count(); i++)
{
observerDelegete(bestanden[i]);
File.Copy(bestanden[i], uploadPlaats[i]);
}
}
catch (UnauthorizedAccessException uoe) { }
catch (FileNotFoundException fnfe) { }
catch (Exception e) { }
}
Delgate
public delegate void ObserverDelegate(string fileName);
Assuming that it's the update of the label that's failing you need to marshal the event onto the UI thread. To do this change your update code to be:
private void UpdateDownloadLabel(string File)
{
if (labelDownloadProgress.InvokeRequired)
{
labelDownloadProgress.Invoke(new Action(() =>
{
labelDownloadProgress.Text = "Downloading: " + File;
});
}
else
{
labelDownloadProgress.Text = "Downloading: " + File;
}
}
I've ended up creating an extension method for this that I can call - thus reducing the amount of repeated code in my applications:
public static void InvokeIfRequired(this Control control, Action action)
{
if (control.InvokeRequired)
{
control.Invoke(action);
}
else
{
action();
}
}
Which is then called like this:
private void UpdateDownloadLabel(string File)
{
this.labelDownloadProgress.InvokeIfRequired(() =>
labelDownloadProgress.Text = "Downloading: " + File);
}
if UpdateDownloadLabel function is in some control code-file, use pattern like this:
private void UpdateDownloadLabel(string File)
{
this.Invoke(new Action(()=> {
labelDownloadProgress.Text = "Downloading: " + File;
})));
}
You need to invoke assignment on UI thread in order to be able to change something on label.
In C#, how do you make a program only process one thing at a time? I've been working on a patching system, and I think I have the coding all correct but can't test it because a lot of the functions are trying to process all at once when they need to be processing in an order. The program doesn't even let the display shown up before it starts trying to process everything. Because none of them return a value other then the main function all the functions are set to void. I thought about maybe using a return value inside of a loop to make sure the program is finished with that step first before moving on but it still leaves the problem of the program not even showing up until everything is done processing which its suppose to show the progress of everything. Any suggestions of tips?
Edit: I don't know what to post in the code, so im posting all the main functions:
public void DSP_Load(object sender, EventArgs e)
{
if (v1 >= v2)
{
File_Progress_Title.Text = "100%";
Update_Status.Text = "Divine Shadows is currently up to date.";
Application.DoEvents();
Process.Start("Divine Shadows.exe");
Close();
}
else
{
Update_Status.Text = "Checking For Updates...";
Application.DoEvents();
if (!Directory.Exists(tempFilePath))
{
Directory.CreateDirectory(tempFilePath);
}
using (SqlCon = new MySqlConnection(connString))
{
SqlCon.Open();
string command = "SELECT * FROM version where version > '" + v1 + "' ORDER BY version LIMIT 1";
MySqlCommand GetLatestVersion = new MySqlCommand(command, SqlCon);
using (MySqlDataReader DR = GetLatestVersion.ExecuteReader())
{
while(DR.Read())
{
do
{
string LatestVersion = Convert.ToString(DR.GetValue(1));
string WebURL = Convert.ToString(DR.GetValue(2));
update.DownloadFileAsync(new Uri(WebURL), tempFilePath + "patch" + LatestVersion + ".zip");
update.DownloadProgressChanged += new DownloadProgressChangedEventHandler(download);
update.DownloadFileCompleted += new AsyncCompletedEventHandler(extration);
Application.Restart();
}
while (v1 < v2);
Process.Start("Divine Shadows.exe");
Close();
}
}
}
}
}
public void download(object sender, DownloadProgressChangedEventArgs e)
{
if (v1 >= v2)
{
File_Progress_Title.Text = "100%";
Update_Status.Text = "Divine Shadows is currently up to date.";
Application.DoEvents();
Process.Start("Divine Shadows.exe");
Close();
}
else
{
Update_Status.Text = "Downloading Updates...";
Application.DoEvents();
File_Progress_Display.Value = e.ProgressPercentage;
File_Progress_Title.Text = Convert.ToString(e.ProgressPercentage) + "%";
}
}
public void extration(object sender, AsyncCompletedEventArgs e)
{
if (v1 >= v2)
{
File_Progress_Title.Text = "100%";
Update_Status.Text = "Divine Shadows is currently up to date.";
Application.DoEvents();
Process.Start("Divine Shadows.exe");
Close();
}
else
{
Update_Status.Text = "Installing Updates, Please Wait...";
Application.DoEvents();
UnzipFile(extactFile, extractLocation);
}
}
public static void UnzipFile(string extactFile, string extractLocation)
{
try
{
FastZip fastZip = new FastZip();
fastZip.CreateEmptyDirectories = false;
fastZip.ExtractZip(extactFile, extractLocation, FastZip.Overwrite.Always, null, null, null, false);
}
catch (Exception ex)
{
throw new Exception("Error unzipping file \"" + extactFile + "\"", ex);
}
File.Delete(extactFile);
}
Your problem is not WebClient() specific, its about how your application is working with threads.
In general, winforms applications have one GUI Thread. This thread is used to executed your methods and also updating the user interface. If you start a long term process, the gui thread gets locked till the operation is finished. Thats the reason why your display is not shown.
You can solve that problem by implementing the BackgroundWorker. On that website you can also find an example how to implement it. Let the BackgroundWorker do your patching process and use events inside the BackgroundWorker.RunWorkerAsync() method to update your GUI.
If you are using c#4 or newer you can use the Task Parallel Library to perform tasks asynchronously, thus leaving your UI response while thing are being downloaded. First of all you need a reference:
using System.Threading.Tasks;
And some code:
public void YourMainFunction()
{
var urls = new List<string>();
urls.Add("http://google.com");
urls.Add("http://yahoo.com");
foreach(var url in urls)
{
Task.Factory.StartNew<DownloadResult>(() =>
DownloadIt(url))
.ContinueWith(WorkDone, TaskScheduler.FromCurrentSynchronizationContext());
}
}
private class DownloadResult
{
public string Url {get; set;}
public string Result {get; set;}
}
private DownloadResult DownloadIt(string url)
{
var downloadResult = new DownloadResult{ Url = url };
var client = new WebClient();
downloadResult.Result = client.DownloadString(url);
return downloadResult;
}
private void WorkDone(Task<DownloadResult> task)
{
if(task.IsFaulted)
{
//An exception was thrown
MessageBox.Show(task.Exception.ToString());
return;
}
//Everything went well
var downloadResult = task.Result;
//Here you can update your UI to reflect progress.
MessageBox.Show(downloadResult.Result);
}