Asynchronous tasks in C# console application combined with Windows.Forms - c#

Let's say I have 10 tasks which should run simultaneously visualizing their progress using GUI. For simplicity let them be just counters 1 to 100. Main app should be console application. GUI controls should be generated on the fly.
Here's the code I've written:
static void Main(string[] args)
{
Enumerable.Range(0, 10).ToList().ForEach(x =>
frm.Controls.Add(new Label()
{
Left = 10,
Top = 22 * x,
}));
Thread mThread = new Thread(delegate ()
{
frm.ShowDialog();
});
mThread.SetApartmentState(ApartmentState.STA);
mThread.Start();
Jobs();
}
static void Jobs()
{
Task[] tasks = new Task[10];
for (int i = 0; i < tasks.Length; i++)
{
tasks[i] = PayLoad(i);
}
Task.WaitAll(tasks);
}
static async Task PayLoad(int taskNumber)
{
Console.WriteLine($"Task {taskNumber} is starting...");
//await Task.Delay(1000);
for (int i = 1; i < 100; i++)
{
Console.WriteLine($"Task {taskNumber} - iteration {i} ");
Action act = delegate () { frm.Controls[taskNumber].Text = i.ToString(); };
frm.BeginInvoke(act);
await Task.Delay(1000);
}
}
The problem is the progress stops when all counters reach 1. If I comment the 2nd await the code completes but I can see it works synchronously. All the labels are filled one by one.
Am I missing something?

Related

Runing one thread at a time on TaskScheduler (STA thread)

I have set StaTaskScheduler threads to 1 and I expected that I would get one Debug output every 5 seconds, but I end up with 10 with the same date
private void Test() {
for (int i = 0; i < 10; i++)
Task.Factory.StartNew(() =>
{
Task.Delay(5000); //temp for long operation
Debug.WriteLine(DateTime.Now);
}, CancellationToken.None, TaskCreationOptions.None, MainWindow.MyStaThread);
}
public static StaTaskScheduler MyStaThread =
new StaTaskScheduler(numberOfThreads: 1);
What am I missing? The reason for STA is that later it will be used for Icons extraction needing STA, but this test is to check it is done in sequence.
you have to start tasks using the MyStaThred.QueueTask rather then Task.Factory.Startnew:
private void Test() {
for (int i = 0; i < 10; i++)
MyStaThread.QueueTask(new Task(() =>
{
Task.Delay(5000); //temp for long operation
Debug.WriteLine(DateTime.Now);
}));
}
public static StaTaskScheduler MyStaThread =
new StaTaskScheduler(numberOfThreads: 1);
Task.Factory.Startnew uses .Net Framework internal thread pool and does not take the StaTaskScheduler into account.

How to perform two tasks at once

I have a card in image format with the front and back side, I intend to show both sides and I created a method with thread in the period of a few seconds to show each side. The problem is that it simply shows one side and I want to see both sides within a minimum of 5 seconds
Thread t1 = new Thread(() =>
{
int numberOfSeconds = 0;
while (numberOfSeconds < 5)
{
Thread.Sleep(10);
numberOfSeconds++;
}
ImgCCF.Source = ImageSource.FromResource("Agtmovel.Img.cartFront.png");
});
Thread t2 = new Thread(() =>
{
int numberOfSeconds = 0;
while (numberOfSeconds < 8)
{
Thread.Sleep(10);
numberOfSeconds++;
}
ImgCCF.Source = ImageSource.FromResource("Agtmovel.Img.cartBack.png");
});
t1.Start();
t2.Start();
//t1.Join();
//t2.Join();
First of all avoid using directly Thread and use Task instead. They are easier to use and they better handle threads.
So you can do that like this:
private async Task FlipImagesAsync()
{
while (true)
{
await Task.Delay(5000); // I'm not entirely sure about the amount of seconds you want to wait here
Device.BeginInvokeOnMainThread(() =>
{
ImgCCF.Source = ImageSource.FromResource("Agtmovel.Img.cartFront.png");
ImgCCF.IsVisible = true;
ImgCCV.IsVisible = false;
});
await Task.Delay(8000); // I'm not entirely sure about the amount of seconds you want to wait here
Device.BeginInvokeOnMainThread(() =>
{
ImgCCV.Source = ImageSource.FromResource("Agtmovel.Img.cartBack.png");
ImgCCV.IsVisible = true;
ImgCCF.IsVisible = false;
});
}
}
Device.BeginInvokeOnMainThread is necessary so that that change is done on the UI thread.
You can call it by using Task.Run(this.FlipImagesAsync());
HIH

Tasks are starting sequencial instead of parallel

I need to start tasks in parallel, but I choose to use Task.Run instead of Parallel.Foreach, so I can get some feedback when all tasks finished and enable UI controls.
private async void buttonStart_Click(object sender, EventArgs e)
{
var cells = objectListView.CheckedObjects;
if(cells != null)
{
List<Task> tasks = new List<Task>();
foreach (Cell c in cells)
{
Cell cell = c;
var progressHandler = new Progress<string>(value =>
{
cell.Status = value;
});
var progress = progressHandler as IProgress<string>;
Task t = Task.Run(() =>
{
progress.Report("Starting...");
int a = 123;
for (int i = 0; i < 200000; i++)
{
a = a + i;
Task.Delay(500).Wait();
}
progress.Report("Done");
});
tasks.Add(t);
}
await Task.WhenAll(tasks);
Console.WriteLine("Done, enabld UI controls");
}
}
So what I expect is that I see in UI "Starting..." almost instantly for all items. What I actually see is first 4 items are "Starting..." (I guess because all 4 CPU cores are used per thread), then each second or less new item is "Starting". I have total 37 items and it takes around 30 seconds for all items to start all tasks.
How can I make it as parallel as possible?
How can I make it as parallel as possible?
The part of inner for loop is simulating long running CPU-bound job, which I would like to start at the same time as much as possible.
It's already as parallel as possible. Starting 37 threads that all have CPU-bound work to do will not make it go any faster, since you're apparently running it on a 4-core machine. There are 4 cores, so only 4 threads can actually run at a time. The other 33 threads are going to be waiting while 4 are running. They would only appear to run simultaneously.
That said, if you really want to start up all those thread pool threads, you can do this by calling ThreadPool.SetMinThreads.
I need to start tasks in parallel, but I choose to use Task.Run instead of Parallel.Foreach, so I can get some feedback when all tasks finished and enable UI controls.
Since you have parallel work to do, you should use Parallel. If you want the nice resume-on-the-UI-thread behavior of await, then you can use a single await Task.Run, something like this:
private async void buttonStart_Click(object sender, EventArgs e)
{
var cells = objectListView.CheckedObjects;
if (cells == null)
return;
var workItems = cells.Select(c => new
{
Cell = c,
Progress = new Progress<string>(value => { c.Status = value; }),
}).ToList();
await Task.Run(() => Parallel.ForEach(workItems, item =>
{
var progress = item.Progress as IProgress<string>();
progress.Report("Starting...");
int a = 123;
for (int i = 0; i < 200000; i++)
{
a = a + i;
Thread.Sleep(500);
}
progress.Report("Done");
}));
Console.WriteLine("Done, enabld UI controls");
}
I'd say, it is as parallel as possible. If you have 4 cores, you can run 4 threads in parallel.
If you can do stuff while waiting for the "delay", have a look into asynchronous programming (where one thread can run multiple tasks "at once", because most of them are waiting for something).
EDIT: you can also run Parallel.ForEach in its own task and await that:
private async void buttonStart_Click(object sender, EventArgs e)
{
var cells = objectListView.CheckedObjects;
if(cells != null)
{
await Task.Run( () => Parallel.ForEach( cells, c => ... ) );
}
}
I think it relies on your taskcreation-options.
TaskCreationOptions.LongRunning
Here you can find further informations:
https://msdn.microsoft.com/en-us/library/system.threading.tasks.taskcreationoptions(v=vs.110).aspx
But you have to know, that task uses a threadpool with a finite maximum amount of threads. You can use LongRunning to signal, that this task needs a long time and should not clog your pool. I thinks it's more complex to create a long-running task, because the scheduler may create a new thread.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace TaskTest
{
internal class Program
{
private static void Main(string[] args)
{
var demo = new Program();
demo.SimulateClick();
Console.ReadLine();
}
public void SimulateClick()
{
buttonStart_Click(null, null);
}
private async void buttonStart_Click(object sender, EventArgs e)
{
var tasks = new List<Task>();
for (var i = 0; i < 36; i++)
{
var taskId = i;
var t = Task.Factory.StartNew((() =>
{
Console.WriteLine($"Starting Task ({taskId})");
for (var ii = 0; ii < 200000; ii++)
{
Task.Delay(TimeSpan.FromMilliseconds(500)).Wait();
var s1 = new string(' ', taskId);
var s2 = new string(' ', 36-taskId);
Console.WriteLine($"Updating Task {s1}X{s2} ({taskId})");
}
Console.Write($"Done ({taskId})");
}),TaskCreationOptions.LongRunning);
tasks.Add(t);
}
await Task.WhenAll(tasks);
Console.WriteLine("Done, enabld UI controls");
}
}
}

Repeat a task (TPL) in windows service, using ContinueWith

I have a windows service (written in C#) that use the task parallel library dll to perform some parallel tasks (5 tasks a time)
After the tasks are executed once I would like to repeat the same tasks on an on going basis (hourly). Call the QueuePeek method
Do I use a timer or a counter like I have setup in the code snippet below?
I am using a counter to set up the tasks, once I reach five I exit the loop, but I also use a .ContinueWith to decrement the counter, so my thought is that the counter value would be below 5 hence the loop would continue. But my ContinueWith seems to be executing on the main thread and the loop then exits.
The call to DecrementCounter using the ContinueWith does not seem to work
FYI : The Importer class is to load some libraries using MEF and do the work
This is my code sample:
private void QueuePeek()
{
var list = SetUpJobs();
while (taskCounter < 5)
{
int j = taskCounter;
Task task = null;
task = new Task(() =>
{
DoLoad(j);
});
taskCounter += 1;
tasks[j] = task;
task.ContinueWith((t) => DecrementTaskCounter());
task.Start();
ds.SetJobStatus(1);
}
if (taskCounter == 0)
Console.WriteLine("Completed all tasks.");
}
private void DoLoad(int i)
{
ILoader loader;
DataService.DataService ds = new DataService.DataService();
Dictionary<int, dynamic> results = ds.AssignRequest(i);
var data = results.Where(x => x.Key == 2).First();
int loaderId = (int)data.Value;
Importer imp = new Importer();
loader = imp.Run(GetLoaderType(loaderId));
LoaderProcessor lp = new LoaderProcessor(loader);
lp.ExecuteLoader();
}
private void DecrementTaskCounter()
{
Console.WriteLine(string.Format("Decrementing task counter with threadId: {0}",Thread.CurrentThread.ManagedThreadId) );
taskCounter--;
}
I see a few issues with your code that can potentially lead to some hard to track-down bugs. First, if using a counter that all of the tasks can potentially be reading and writing to at the same time, try using Interlocked. For example:
Interlocked.Increment(ref _taskCounter); // or Interlocked.Decrement(ref _taskCounter);
If I understand what you're trying to accomplish, I think what you want to do is to use a timer that you re-schedule after each group of tasks is finished.
public class Worker
{
private System.Threading.Timer _timer;
private int _timeUntilNextCall = 3600000;
public void Start()
{
_timer = new Timer(new TimerCallback(QueuePeek), null, 0, Timeout.Infinite);
}
private void QueuePeek(object state)
{
int numberOfTasks = 5;
Task[] tasks = new Task[numberOfTasks];
for(int i = 0; i < numberOfTasks; i++)
{
tasks[i] = new Task(() =>
{
DoLoad();
});
tasks[i].Start();
}
// When all tasks are complete, set to run this method again in x milliseconds
Task.Factory.ContinueWhenAll(tasks, (t) => { _timer.Change(_timeUntilNextCall, Timeout.Infinite); });
}
private void DoLoad() { }
}

Why Task does not start without Wait

When I create a Task :
for (int i = 0; i < 5; i++)
{
// var testClient =
Task.Factory.StartNew(
() =>
{
TaskClient();
});
}
public static void TaskClient()
{
System.Console.WriteLine("--------------------");
}
But this does not start the Console Write Untill I wait for the task!!!
Task.Factory.StartNew(
() =>
{
TaskClient();
}).Wait();
Why do we need to call Wait , When I am already starting the thread using StartNew
#vcsjones has to be right. You don't see the result because program ended and window was closed.
I've tried your code and if I run the program from cmd, without debugger I can see the correct output. To make it a little more meaningful I've added another Console.WriteLine at the end of Main method:
for (int i = 0; i < 5; i++)
{
// var testClient =
Task.Factory.StartNew(
() =>
{
TaskClient();
});
}
Console.WriteLine("End of program execution.");
Returns:
End of program execution.
--------------------
--------------------
--------------------
--------------------
--------------------
As you can see, it works just fine.
If you want to wait with further execution untill all tasks are done, you can use Task.WaitAll static method:
var tasks = new Task[5];
for (int i = 0; i < 5; i++)
{
// var testClient =
tasks[i] = Task.Factory.StartNew(
() =>
{
TaskClient();
});
}
Task.WaitAll(tasks);

Categories

Resources