I've written this sample code for a long running process, but the Windows Form freezes until the process completes. How do I change the code so that the work runs in parallel?
var ui = TaskScheduler.FromCurrentSynchronizationContext();
Task t = Task.Factory.StartNew(delegate
{
textBox1.Text = "Enter Thread";
for (int i = 0; i < 20; i++)
{
//My Long Running Work
}
textBox1.Text = textBox1.Text + Environment.NewLine + "After Loop";
}, CancellationToken.None, TaskCreationOptions.None, ui);
You can use a continuation. I don't remember the exact syntax but it's something like:
textBox1.Text = "Enter Thread"; //assuming here we're on the UI thread
Task t = Task.Factory.StartNew(delegate
{
for (int i = 0; i < 20; i++)
{
//My Long Running Work
}
return result;
})
.ContinueWith(ret => textBox1.Text = textBox1.Text + Environment.NewLine + result,
TaskScheduler.FromCurrentSynchronizationContext());
An alternative would be something like:
Task t = Task.Factory.StartNew(delegate
{
YourForm.Invoke((Action)(() => textBox1.Text = "Enter Thread");
for (int i = 0; i < 20; i++)
{
//My Long Running Work
}
YourForm.Invoke((Action)(() => textBox1.Text = textBox1.Text + Environment.NewLine + result);},
CancellationToken.None, TaskCreationOptions.None);
Again, I don't remember the exact syntax but the idea is that you want to perform the long operation on a thread different than the UI thread, but report progress (including completion) on the UI thread.
By the way, the BackGroundWorker class would work very well here, too (I personally like it very much).
Related
I'm trying to show a progress dialog showing the percent done with the method to wait everything + an await Task.Delay(20); And an await for the method I want to execute. Now I notice that with that task.delay the execution takes much longer.
What I want to achieve instead is that the progress.dialog calculates how long the method takes instead of putting a delay on it because this works a bit more slowly.
What are my options in this?
This is my code
private async Task DownloadAllAlert()
{
//alert to download everything
bool result = await DisplayAlert("Download", "Do you want to download everything?", "Yes", "No"); ;
//alert is user chose yes
if (result)
{
// loading dialog in percentage till downloading is done
using (var progress = UserDialogs.Instance.Progress("Loading..."))
{
for (var i = 0; i < 100; i++)
{
progress.PercentComplete = i;
await Api.DownloadAll();
await Task.Delay(20);
}
}
}
}
try using Task in c#.
using (var progress = UserDialogs.Instance.Progress("Loading..."))
{
await LoadData(ref progress);
}
call this function
public async Task LoadData(ref IProgressDialog progress);
{
//await Task.Yield(); //add this line of code if on ios didnt work
Task task1 = new Task(() =>
{
for (int i = 0; i < 50 ; i++) // loop untill 50% then increase sleeping thread time
{
progress.PercentComplete = i;
//sleeping for 1 second
Thread.Sleep(1000);
}
for (int i = 50; i < 100; i++) // loop untill 99% then stop
{
progress.PercentComplete = i;
//sleeping for 2 second
Thread.Sleep(2000);
}
Console.WriteLine("Task 1 complete");
});
Task task2 = new Task(() =>
{
await Api.DownloadAll();
Console.WriteLine("Task 2 complete");
});
//starting the tasks
task1.Start();
task2.Start();
Task.WaitAny(task2);
progress.PercentComplete = 100; // when task2 finish put it 100%
}
I've been using Progress<T> and wondered if it can be replaced by Action<T>.
In the code below, using each of them for reporting progress, i.e. ReportWithProgress() or ReportWithAction(), didn't make any noticeable difference to me. How progressBar1 increased, how the strings were written on the output window, they seemed the same.
// WinForm application with progressBar1
private void HeavyIO()
{
Thread.Sleep(20); // assume heavy IO
}
private async Task ReportWithProgress()
{
IProgress<int> p = new Progress<int>(i => progressBar1.Value = i);
for (int i = 0; i <= 100; i++)
{
await Task.Run(() => HeavyIO());
Console.WriteLine("Progress : " + i);
p.Report(i);
}
}
private async Task ReportWithAction()
{
var a = new Action<int>(i => progressBar1.Value = i);
for (int i = 0; i <= 100; i++)
{
await Task.Run(() => HeavyIO());
Console.WriteLine("Action : " + i);
a(i);
}
}
But Progress<T> can't be a reinvention of the wheel. There should be a reason why it was implemented. Googling "c# Progress vs Action" didn't give me much help. How is Progress different from Action?
Calling progressBar1.Value = i from a different thread results in the dreaded "cross-thread operation not valid" exception. The Progress class, on the other hand, dispatches the event to the synchronization context captured in the moment of construction:
// simplified code, check reference source for actual code
void IProgress<T>.Report(T value)
{
// post the processing to the captured sync context
m_synchronizationContext.Post(InvokeHandlers, value);
}
private void InvokeHandlers(object state)
{
// invoke the handler passed through the constructor
m_handler?.Invoke((T)state);
// invoke the ProgressChanged event handler
ProgressChanged?.Invoke(this, (T)state);
}
This ensures that all updates to progress bars, labels and other UI elements are done on a (one and only) GUI thread.
So, it only makes sense to instantiate the Progress class outside of the background thread, inside a method which is called on a UI thread:
void Button_Click(object sender, EventArgs e)
{
// since this is a UI event, instantiating the Progress class
// here will capture the UI thread context
var progress = new Progress<int>(i => progressBar1.Value = i);
// pass this instance to the background task
Task.Run(() => ReportWithProgress(progress));
}
async Task ReportWithProgress(IProgress<int> p)
{
for (int i = 0; i <= 100; i++)
{
await Task.Run(() => HeavyIO());
Console.WriteLine("Progress : " + i);
p.Report(i);
}
}
The difference is that with a Progress<T> you have an event where multiple listeners can listen for progress and Progress<T> does capture the SynchonizationContext when the instance is constructed and thus does not need to be invoked to the GUI-thread if created in the GUI-thread.
You can also add multiple listeners to an Action<T> (thanks to #Servy for pointing that out), but each of them are then executed in the thread which invokes the action.
Think of the following extended example, where the Progress<T> will work, but the Action<T> will throw an exception:
private async Task ReportWithProgress()
{
var p = new Progress<int>(i => progressBar1.Value = i);
p.ProgressChanged += (s, e) => progressBar2.Value = e;
Task.Run(() =>
{
for (int i = 0; i <= 100; i++)
{
await Task.Run(() => HeavyIO());
Console.WriteLine("Progress : " + i);
((IProgress<int>)p).Report(i);
}
});
}
private async Task ReportWithAction()
{
var a = new Action<int>(i => progressBar1.Value = i);
a += i => progressBar2.Value = i;
Task.Run(() =>
{
for (int i = 0; i <= 100; i++)
{
await Task.Run(() => HeavyIO());
Console.WriteLine("Action : " + i);
a(i);
}
});
}
I have a project at school to make a WPF project which makes encryption and decryption of an input text. I want the application to be responsive but it always freeze.
I want to use TPL and I use TaskScheduler.FromCurrentSynchronizationContext() but it is not working. I don't want to use Dispatcher or something else what is specific only to WPF.
tokenSource = new CancellationTokenSource();
int lineCount = textBoxInput.LineCount;
string encryptTextInput = "";
List<string> listText = new List<string>();
List<Task> listTask = new List<Task>();
var ui = TaskScheduler.FromCurrentSynchronizationContext();
for (int cnt = 0; cnt < lineCount; cnt++)
{
encryptTextInput = textBoxInput.GetLineText(cnt);
listText.Add(encryptTextInput);
}
for (int cnt = 0; cnt < lineCount; cnt++)
{
int line = cnt;
var myTask = Task.Factory.StartNew(result =>
{
return EncryptDecrypt.Encrypt(listText[line]);
}, tokenSource.Token);
listTask.Add(myTask);
var display = myTask.ContinueWith(resultTask =>
textBoxOutput.Text += myTask.Result.ToString(), CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, ui);
var displayCancel = myTask.ContinueWith(resultTask =>
textBoxOutput.Text += myTask.Result.ToString(), CancellationToken.None, TaskContinuationOptions.OnlyOnCanceled, ui);
}
Refactored method which relates to Encryption. Please, see the comments related to the code below:
private async void buttonEncrypt_Click(object sender, RoutedEventArgs e)
{
string elapsedTime = string.Empty;
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
tokenSource = new CancellationTokenSource();
int lineCount = textBoxInput.LineCount;
var outputResult = String.Empty;
for (int cnt = 0; cnt < lineCount; cnt++)
{
var lineToProcess = textBoxInput.GetLineText(cnt);
//Code inside task will work in thread from thread pool, so the UI thread shouldn't be blocked
string result = await Task.Run(() =>
EncryptDecrypt.Encrypt(lineToProcess), tokenSource.Token);
outputResult += result;
}
//UI thread: when completed update the UI with encrypted text.
textBoxOutput.Text = outputResult;
stopWatch.Stop();
TimeSpan ts = stopWatch.Elapsed;
elapsedTime = String.Format("{0:00}:{1:00}:{2:00}:{3:00}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds / 10);
time.Content = elapsedTime;
}
A couple of comments related to the code above.
The code working in the following way:
Read lines from textbox line by line.
Processes each line one by one
(in thread pool context) in order the lines are in input
When processing of all lines completed, add the result of encryption to the
output textbox
The problem in previous code was that the UI thread was accessing too frequently and this lead to UI freezing during processing.
Now the processing is going in background thread and rendered on UI only when all processing completes.
Also, I recommend you to add some kind of indicator to inform the user that the input is processing: progress bar or something other.
I'm trying to make a tool that get source string from many URL I provided. And I use this code for multithreading
new Thread(() =>
{
while (stop != true)
{
if (nowworker >= threads)
{
Thread.Sleep(50);
}
else
{
if (i <= urllist.Count - 1)
{
var thread = new Thread(() =>
{
string source = GetSource(urllist[i]);
SaveToFile(source, i + ".txt");
});
thread.Start();
i++;
nowworker += 1;
}
else
{
stop = true;
}
}
}
}).Start();
It's run very smooth until I check the result and have some duplicated result and missing some url I provided if using less thread for many url(10 thread - 20 url) but there's no problem when using 20 thread for 20 url.
Please help me. Thank you.
if (i <= urllist.Count - 1)
{
var thread = new Thread(() =>
{
string source = GetSource(urllist[i]);
SaveToFile(source, i + ".txt");
});
thread.Start();
i++;
nowworker += 1;
}
The method you're passing to the thread is not guaranteed to execute before i is updated (the i++). Infact, it's very unlikely that it will. This means that multiple threads may use the same value of i, and some values of i will not have any threads executing it.
Even worse, GetSource may use a different value of i than SaveToFile.
Have a readup here: http://jonskeet.uk/csharp/csharp2/delegates.html
This will fix it:
if (i <= urllist.Count - 1)
{
var currentIndex = i;
var thread = new Thread(() =>
{
string source = GetSource(urllist[currentIndex]);
SaveToFile(source, currentIndex + ".txt");
});
thread.Start();
i++;
nowworker += 1;
}
Even better, you can replace the entire block of code with this:
Parallel.For(0, urlList.Count - 1,
new ParallelOptions { MaxDegreeOfParallelism = threads },
i =>
{
string source = GetSource(urllist[i]);
SaveToFile(source, i + ".txt");
}
);
Which will get rid of the code-smelly Thread.Sleep() and let .NET manage spinning up threads for you
Recently I started working on trying to mass-scrape a website for archiving purposes and I thought it would be a good idea to have multiple web requests working asynchronously to speed things up (10,000,000 pages is definitely a lot to archive) and so I ventured into the harsh mistress of parallelism, three minutes later I start to wonder why the tasks I'm creating (via Task.Factory.StartNew) are 'clogging'.
Annoyed and intrigued I decided to test this to see if it wasn't just a result of circumstance, so I created a new console project in VS2012 and created this:
static void Main(string[] args)
{
for (int i = 0; i < 10; i++) {
int i2 = i + 1;
Stopwatch t = new Stopwatch();
t.Start();
Task.Factory.StartNew(() => {
t.Stop();
Console.ForegroundColor = ConsoleColor.Green; //Note that the other tasks might manage to write their lines between these colour changes messing up the colours.
Console.WriteLine("Task " + i2 + " started after " + t.Elapsed.Seconds + "." + t.Elapsed.Milliseconds + "s");
Thread.Sleep(5000);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Task " + i2 + " finished");
});
}
Console.ReadKey();
}
That when run came up with this result:
As you can see the first four tasks start within quick succession with times of ~0.27, however after that the tasks start to drastically increase in the time it takes them to start.
Why is this happening and what can I do to fix or get around this limitation?
The tasks (by default) runs on the threadpool, which is just as it sounds, a pool of threads. The threadpool is optimized for a lot of situations, but throwing Thread.Sleep in there probably throws a wrench in most of them. Also, Task.Factory.StartNew is a generally a bad idea to use, because people doesn't understand how it works. Try this instead:
static void Main(string[] args)
{
for (int i = 0; i < 10; i++) {
int i2 = i + 1;
Stopwatch t = new Stopwatch();
t.Start();
Task.Run(async () => {
t.Stop();
Console.ForegroundColor = ConsoleColor.Green; //Note that the other tasks might manage to write their lines between these colour changes messing up the colours.
Console.WriteLine("Task " + i2 + " started after " + t.Elapsed.Seconds + "." + t.Elapsed.Milliseconds + "s");
await Task.Delay(5000);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Task " + i2 + " finished");
});
}
Console.ReadKey();
}
More explanation:
The threadpool has a limited number of threads at it's disposal. This number changes depending on certain conditions, however, in general it holds true. For this reason, you should never do anything blocking on the threadpool (if you want to achieve parallelism that is). Thread.Sleep is a perfect example of a blocking API, but so is most web request APIs, unless you use the newer async versions.
So the problem in your original program with crawling is probably the same as in the sample you posted. You are blocking all the thread pool threads, and thus it's getting forced to spin up new threads, and ends up clogging.
Extra goodies
Coincidentally, using Task.Run in this way also easily allows you to rewrite the code in such a way that you can know when it's complete. By storing a reference to all of the started tasks, and awaiting them all at the end (this does not prevent parallelism), you can reliably know when all the tasks have completed. The following shows how to achieve that:
static void Main(string[] args)
{
var tasks = new List<Task>();
for (int i = 0; i < 10; i++) {
int i2 = i + 1;
Stopwatch t = new Stopwatch();
t.Start();
tasks.Add(Task.Run(async () => {
t.Stop();
Console.ForegroundColor = ConsoleColor.Green; //Note that the other tasks might manage to write their lines between these colour changes messing up the colours.
Console.WriteLine("Task " + i2 + " started after " + t.Elapsed.Seconds + "." + t.Elapsed.Milliseconds + "s");
await Task.Delay(5000);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Task " + i2 + " finished");
}));
}
Task.WaitAll(tasks.ToArray());
Console.WriteLine("All tasks completed");
Console.ReadKey();
}
Note: this code has not been tested
Read more
More info on Task.Factory.StartNew and why it should be avoided: http://blog.stephencleary.com/2013/08/startnew-is-dangerous.html.
I think this is occurring because you have exhausted all available threads in the thread pool. Try starting your tasks using TaskCreationOptions.LongRunning. More details here.
Another problem is that you are using Thread.Sleep, this blocks the current thread and its a waste of resources. Try waiting asynchronously using await Task.Delay. You may need to change your lambda to be async.
Task.Factory.StartNew(async () => {
t.Stop();
Console.ForegroundColor = ConsoleColor.Green; //Note that the other tasks might manage to write their lines between these colour changes messing up the colours.
Console.WriteLine("Task " + i2 + " started after " + t.Elapsed.Seconds + "." + t.Elapsed.Milliseconds + "s");
await Task.Delay(5000);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Task " + i2 + " finished");
});