I have a program that contains 2 listbox, this program is bassed to search file, and then compare with a StopWatch the difference to use AsyncAwait and TPL... The first listbox does the function using AsyncAwait (I don't know if it's the better way to do but it works, see my code below)
private async void button1_Click(object sender, EventArgs e)
{
Stopwatch stopWatch = new Stopwatch();
foreach (string d in Directory.GetDirectories(#"C:\Visual Studio Projectes\Hash\AsyncAwait\Carpetes"))
{
foreach (string s in Directory.GetFiles(d))
{
stopWatch.Start();
listBox1.Items.Add(s);
await Task.Delay(1);
btIniciar1.Enabled = false;
}
}
btIniciar1.Enabled = true;
stopWatch.Stop();
TimeSpan ts = stopWatch.Elapsed;
textBox1.Text = ts.ToString("mm\\:ss\\.ff") + (" minuts");
}
And then in my second listbox is where I'm stuck, I don't know how to implement the Parallel.ForEach to act like async, what's the better way to do this? I can't find the way to use TPL in this case to do the same as my first listbox, could you help me please?
There's no point in using async in your example code, since it's not actually doing anything asynchronously. If you want to wrap the synchronous code in a background thread, use Task.Run.
Regarding Parallel.ForEach, you can treat it asynchronously by wrapping it in Task.Run: await Task.Run(() => Parallel.ForEach(...));
Note that parallel/background threads cannot directly access UI elements. You can use IProgress<T>/Progress<T> if you want to update the UI from a background/threadpool thread.
Update:
The serial code would look like:
private async void button1_Click(object sender, EventArgs e)
{
IProgress<string> progress = new Progress<string>(update =>
{
listBox1.Items.Add(s);
btIniciar1.Enabled = false;
});
var ts = await Task.Run(() =>
{
Stopwatch stopWatch = new Stopwatch();
foreach (string d in Directory.GetDirectories(#"C:\Visual Studio Projectes\Hash\AsyncAwait\Carpetes"))
{
foreach (string s in Directory.GetFiles(d))
{
stopWatch.Start();
progress.Report(s);
}
}
stopWatch.Stop();
return stopWatch.Elapsed;
});
btIniciar1.Enabled = true;
textBox1.Text = ts.ToString("mm\\:ss\\.ff") + (" minuts");
}
The parallel code would look like:
private async void button1_Click(object sender, EventArgs e)
{
IProgress<string> progress = new Progress<string>(update =>
{
listBox1.Items.Add(s);
btIniciar1.Enabled = false;
});
var ts = await Task.Run(() => Parallel.ForEach( ...
));
btIniciar1.Enabled = true;
textBox1.Text = ts.ToString("mm\\:ss\\.ff") + (" minuts");
}
Finally I've solved this issue doing this :
DirectoryInfo nodeDir = new DirectoryInfo(#"c:\files");
Parallel.ForEach(nodeDir.GetDirectories(), async dir =>
{
foreach (string s in Directory.GetFiles(dir.FullName))
{
Invoke(new MethodInvoker(delegate { lbxParallel.Items.Add(s); }));
contador++;
await Task.Delay(1);
}
}
Related
I used to be weary of using BackgroundWorker because it required so many functions to work correctly. However when I swapped to C# from VB.NET (about a month ago) I stumbled across a really easy way to instance them;
Example;
private void cmdMaxCompressPNG_Click(object sender, EventArgs e) {
pbStatus.Maximum = lstFiles.Items.Count;
List<string> FileList = Load_Listbox_Data();
var bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.DoWork += delegate {
foreach (string FileName in FileList) {
ShellandWait("optipng.exe", String.Format("\"{0}\"", FileName));
bw.ReportProgress(1);
}
};
bw.ProgressChanged += (object s, ProgressChangedEventArgs ex) => {
pbStatus.Value += 1;
};
bw.RunWorkerCompleted += delegate {
lstFiles.Items.Clear();
pbStatus.Value = 0;
MessageBox.Show(text: "Task Complete", caption: "Status Update");
};
bw.RunWorkerAsync();
}
There it is, all in one function! Simple to write, easy to understand, and no real leg work. I even made a Snippet out of it. I've since converted all my multiple part BackgroundWorker functions, into this little piece of elegant code. I've also started using them more liberally than in the past. Yesterday I was reading an article regarding Async and Await and how that's apparently how I should be doing things. I'm having trouble wrapping my head around it.
I've tried to use local functions, but I can't get the wording correct. It keeps trying to put it as synchronous.
How would I convert the above into an equally tight implementation of Await/Async logic?
[Edit]
ShellandWait;
private void ShellandWait(string ProcessPath, string Arguments, bool boolWait = true) {
System.Diagnostics.Process ShellProcess = new System.Diagnostics.Process();
ShellProcess.StartInfo.FileName = ProcessPath;
ShellProcess.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
ShellProcess.StartInfo.Arguments = Arguments;
ShellProcess.StartInfo.CreateNoWindow = true;
ShellProcess.StartInfo.UseShellExecute = false;
ShellProcess.StartInfo.RedirectStandardOutput = true;
ShellProcess.Start();
if (boolWait) { ShellProcess.WaitForExit(); }
if (boolWait) { ShellProcess.Close(); }
}
The original code processes only one file at a time so you could use a simple loop and only execute ShellandAwait asynchronously:
private void cmdMaxCompressPNG_Click(object sender, EventArgs e)
{
pbStatus.Maximum = lstFiles.Items.Count;
var FileList = Load_Listbox_Data();
foreach (var FileName in FileList)
{
//Only thing that needs to run in the background
await Task.Run(()=>ShellandWait("optipng.exe", String.Format("\"{0}\"", FileName));
//Back in the UI
pbStatus.Value += 1;
}
};
lstFiles.Items.Clear();
pbStatus.Value = 0;
MessageBox.Show(text: "Task Complete", caption: "Status Update");
It would be even better if ShellandWait was modified so it *doesn't block. I assume it uses Process.WaitForExit() to block. The method should await asynchronously instead by listening to the Exited event. Such events can be converted to tasks as shown in Tasks and the Event-based Asynchronous Pattern.
The method would look something like this :
Task<string> ShellAsync(string commandPath,string argument)
{
var tcs = new TaskCompletionSource<string>();
var process = new Process();
//Configure the process
//...
process.EnableRaisingEvents = true;
process.Exited += (s,e) => tcs.TrySetResult(argument);
process.Start();
return tcs.Task;
}
This would allow the loop to be simplified to :
foreach (var FileName in FileList)
{
await ShellAsync("optipng.exe", String.Format("\"{0}\"", FileName));
//Back in the UI
pbStatus.Value += 1;
}
The way I went about this (after reading some more) is to use Task.Run()
private async void cmdMaxCompressPNG_Click(object sender, EventArgs e) {
pbStatus.Maximum = lstFiles.Items.Count;
List<string> FileList = Load_Listbox_Data();
await Task.Run(() => {
foreach (string FileName in FileList) {
ShellandWait("optipng.exe", String.Format("\"{0}\"", FileName));
pbStatus.GetCurrentParent().Invoke(new MethodInvoker(delegate { pbStatus.Value += 1; }));
}
});
lstFiles.Items.Clear();
pbStatus.Value = 0;
MessageBox.Show(text: "Task Complete", caption: "Status Update");
}
Note the async next to private.
I had to get fancy with the progress bar since it was a status strip progress bar. If it would have been a standard control I could have used;
pbStatus.Invoke((Action)(() => pbStatus.Value += 1))
Answer to progress bar found at -> Update progress bar from Task.Run async
And here -> How to Invoke the progress bar in Status strip?
I'd consider doing this with Microsoft's Reactive Framework. I think it's much more powerful than using Tasks.
private void cmdMaxCompressPNG_Click(object sender, EventArgs e)
{
pbStatus.Maximum = lstFiles.Items.Count;
var query =
from FileName in Load_Listbox_Data().ToObservable()
from u in Observable.Start(() =>
System.Diagnostics.Process
.Start("optipng.exe", String.Format("\"{0}\"", FileName))
.WaitForExit())
select u;
query
.ObserveOn(this) //marshall back to UI thread
.Subscribe(
x => pbStatus.Value += 1,
() =>
{
lstFiles.Items.Clear();
pbStatus.Value = 0;
MessageBox.Show(text: "Task Complete", caption: "Status Update");
});
}
Just NuGet "System.Reactive.Windows.Forms" and add using System.Reactive.Linq; to get it working.
I have the following threaded code ( i think ) form with a start and cancel button and a multilined text box, the commented sections (//) are from the working single threaded version and below i have tried to retrofit the multithreaded task.factory parts, however it seems to launch the powershell commands fine ( show in task manager ) but the program completes without waiting for the results from each "heavyOperation".
The idea is to start all the four HeavyOperation tasks at the same time (ish) and wait for each to return the results and append the results to the text box
public partial class Form1 : Form
{
Progress<string> progressReporter = new Progress<string>();
CancellationTokenSource cancelSource;
public Form1()
{
InitializeComponent();
progressReporter.ProgressChanged += progressManager_ProgressChanged;
}
async private void btnStart_Click(object sender, EventArgs e)
{
btnStart.Enabled = false;
btnCancel.Enabled = true;
cancelSource = new CancellationTokenSource();
//textBox1.Text = await Task.Run(() => PerfromTaskAction(cancelSource.Token));
await Task.Run(() => PerfromTaskAction(cancelSource.Token));
lblStatus.Text = "Completed."; btnStart.Enabled = true;
btnCancel.Enabled = false;
}
//private string PerfromTaskAction(CancellationToken ct)
static void PerfromTaskAction(CancellationToken ct)
{
//StringBuilder sb = new StringBuilder();
object[] arrObjects = new object[] { "SERVER1", "SERVER2", "SERVER3", "SERVER4" };
foreach(object i in arrObjects)
{
//if (ct.IsCancellationRequested) break;
//sb.Append(string.Format("{0}: {1}\r\n", HeavyOperation(i.ToString()),i));
//((IProgress<string>)progressReporter).Report(string.Format("Now Checking: {0}...", i));
Task.Factory.StartNew(() => HeavyOperation(i.ToString()));
}
//return sb.ToString();
}
void progressManager_ProgressChanged(object sender, string e)
{
lblStatus.Invoke((Action)(() => lblStatus.Text = e));
}
//private string HeavyOperation(string i)
public static void HeavyOperation(string i)
{
PowerShell ps = PowerShell.Create();
ps.AddCommand("invoke-command");
ps.AddParameter("computername", i);
ps.AddParameter("scriptblock", ScriptBlock.Create("get-vmreplication | select State"));
Collection<PSObject> result = ps.Invoke();
//return result[0].Properties["State"].Value.ToString();
Console.Write(result[0].Properties["State"].Value.ToString());
}
private void btnCancel_Click(object sender, EventArgs e)
{
cancelSource.Cancel();
}
}
Thanks for looking
You also need to await for the HeavyOperations to complete.
You can use Task.WhenAll for this purpose. Here is an async version of PerformTaskAction, using Task.WhenAll
I've taken into account Scott Chamberlain's suggestions:
Changed the unsafe (in async-await context) Task.Factory.StartNew() to Task.Run()
Removed the unnecessary await in the invocation of PerformTaskAction()
Passed the missing CancellationToken in the outer Task.Run() call
static async Task PerfromTaskAction(CancellationToken ct) {
//StringBuilder sb = new StringBuilder();
object[] arrObjects = new object[] { "SERVER1", "SERVER2", "SERVER3", "SERVER4" };
IList<Task> tasks = new List<Task>(); // collect all tasks in single collection
foreach( object i in arrObjects ) {
//if (ct.IsCancellationRequested) break;
//sb.Append(string.Format("{0}: {1}\r\n", HeavyOperation(i.ToString()),i));
//((IProgress<string>)progressReporter).Report(string.Format("Now Checking: {0}...", i));
tasks.Add(Task.Run(() => HeavyOperation(i.ToString())));
}
await Task.WhenAll(tasks).ConfigureAwait(false); // wait asynchronously for all tasks to complete
}
Now that PerformTaskAction is async, you also need to await on it.
Finally you invoke PerformTaskAction, making sure you also pass the CancellationToken.
await Task.Run( ()=> PerformTaskAction(cancelSource.Token), cancelSource.Token);
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.
I'm having some trouble accessing the UI from an another thread.
I understand the basics on cross-threading limitations, but I can't seem to write the code that will work. More specifically, I can't access the ListView from a static method (thread).
I'm trying to make it work with backgroundWorker.
Here's my code:
private void start_Click(object sender, EventArgs e)
{
ServicePointManager.DefaultConnectionLimit = 20;
var tasks = new List<Task<int>>();
foreach (ListViewItem item in listView1.Items)
{
string currentUrl = item.SubItems[1].Text;
int i = item.Index;
tasks.Add(Task.Factory.StartNew(() => { return GetWebResponse(currentUrl, i); }));
}
Task.WaitAll(tasks.ToArray());
}
private static int GetWebResponse(string url, int itemIndex)
{
int statusCode = 0;
HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
Task<HttpWebResponse> responseTask = Task.Factory.FromAsync<HttpWebResponse>(httpWebRequest.BeginGetResponse, asyncResult => (HttpWebResponse)httpWebRequest.EndGetResponse(asyncResult), null);
backgroundWorker = new BackgroundWorker();
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.WorkerSupportsCancellation = true;
backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
backgroundWorker.RunWorkerAsync();
return statusCode;
}
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
listView1.Items[0].ImageKey = "green";
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
while (!e.Cancel)
{
Thread.Sleep(5000);
backgroundWorker.ReportProgress(0);
}
}
This code doesn't work because backgroundWorker_DoWork and backgroundWorker_ProgressChanged are not static, but if I make them static then I can't access listView1
EDIT: Got it working. Code below for review
public delegate void delUpdateListView(int itemIndex, int statusCode);
public Form1()
{
InitializeComponent();
}
private void start_Click(object sender, EventArgs e)
{
ServicePointManager.DefaultConnectionLimit = 20;
var tasks = new List<Task<int>>();
foreach (ListViewItem item in listView1.Items)
{
string currentUrl = item.SubItems[1].Text;
int i = item.Index;
tasks.Add(Task.Factory.StartNew(() => {
//return GetWebResponse(currentUrl, i);
int statusCode = 0;
HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(currentUrl);
Task<HttpWebResponse> responseTask = Task.Factory.FromAsync<HttpWebResponse>(httpWebRequest.BeginGetResponse, asyncResult => (HttpWebResponse)httpWebRequest.EndGetResponse(asyncResult), null);
statusCode = (int)responseTask.Result.StatusCode;
object[] invParams = new object[2];
invParams[0] = i;
invParams[1] = statusCode;
if (InvokeRequired)
{
BeginInvoke(new delUpdateListView(UpdateListView), invParams);
}
else
{
Invoke(new delUpdateListView(UpdateListView), invParams);
}
return statusCode;
}));
}
Task.WaitAll(tasks.ToArray());
}
public void UpdateListView(int itemIndex, int statusCode) {
listView1.Items[itemIndex].ImageKey = "green";
}
I see several problems here:
1) I don't see why GetWebResponse needs to be static. The easiest solution would be to make it an instance method.
2) Why are you using the background worker anyway?
3) It doesn't make much sense to use Tasks and then wait for them to finish right after you spawn them. This blocks your application where it should be responsive.
As for 3): To keep the UI responsive and updatable, disable everything the user may not click before spawning the tasks, add a continuation action to each task that re-enables the UI components. The task may update the list using the usual Invoke calls.
Looking to chain a task to a previous instance if it exists. Currently, both are executed at the same time.
Initial code that works for one task :
private async void MenuMediaAddFiles_OnClick(object sender, RoutedEventArgs e)
{
var dialog = GetDefaultOpenFileDialog();
using (dialog)
{
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
using (var progress = new SimpleProgress(this))
{
int addFiles = await _context.AddFiles(dialog.FileNames, progress);
Console.WriteLine("Files added: {0}", addFiles);
}
}
}
}
A failed attempt to make it work :
Task<int> _files;
private async void MenuMediaAddFiles_OnClick(object sender, RoutedEventArgs e)
{
var dialog = GetDefaultOpenFileDialog();
using (dialog)
{
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
using (var progress = new SimpleProgress(this))
{
int addFiles;
Task<int> files = _context.AddFiles(dialog.FileNames, progress);
if (_files == null)
{
_files = files;
}
else
{
var task1 = await _files.ContinueWith(task => _context.AddFiles(dialog.FileNames, new SimpleProgress(this)));
}
addFiles = await _files;
Console.WriteLine("Files added: {0}", addFiles);
}
}
}
}
You were pretty close, but there were a few things that needed to be modified:
private Task<int> previousTask = Task.FromResult(0);
private async void MenuMediaAddFiles_OnClick(object sender, RoutedEventArgs e)
{
var dialog = GetDefaultOpenFileDialog();
using (dialog)
{
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
using (var progress = new SimpleProgress(this))
{
previousTask = previousTask.ContinueWith(t =>
_context.AddFiles(dialog.FileNames, progress))
.UnWrap(); ;
int addFiles = await previousTask;
Console.WriteLine("Files added: {0}", addFiles);
}
}
}
}
Things to note:
Rather than having the previous task be null sometimes, it was easier to initialize it to an already completed task (Task.FromResult(0)). This avoids the null check code.
You were calling AddFiles twice. You shouldn't have been calling it before the if, and you weren't ever assigning the task to the instance field inside the if.
I used UnWrap instead of await to turn the Task<Task<int>> into a Task<int>. Both work, but in this case I felt UnWrap made its intentions clearer.
Note that since the entire event handler will be running in the UI thread there's no need to synchronize access to previousTask, if it doesn't, you'd need to do some locking.