locked
Run tasks one by one asynchronously in windows forms RRS feed

  • Question

  • Hi I wanted to achieve something like below.

    My windows form

    void btn_Click()

    {

    TaskHelper ts = new TaskHelper();

    ts.TaskStarted += team_TaskStarted;

    ts.TaskCompleted += team_TaskCompleted;

    ts.RunAllTasks();

    }

    void team_TaskStarted(object sender,EventArgs e){RefreshUI();}

    void team_TaskCompleted(object sender,EventArgs e){RefreshUI();}

    pubic Class TaskHelper

    {

    public void RunAllTasks()

    {

    foreach (MyTeam task in List<MyTeam>){

    //1. inform UI that task started (an event is raised.)

    //2. start the task(runs for around minute.)

    //3. inform UI that task has finished (an event is raised.)

    //4. ContinueWith next task in the list

    }

    }


    Sunil

    Wednesday, May 8, 2013 5:18 PM

Answers

All replies

  • I have List<System.Threading.Tasks.Task> lst = new List<System.Threading.Tasks.Task>();

    I need to execute one after the other. i.e., as first task finishes then I should continue with second task. second task should continue with Third task so forth until the last task in the list.

    How do I do this?

    Your help is greatly appreciated. Thanks in advance.


    Sunil


    • Edited by Sunil Kumar Reddy Tuesday, May 7, 2013 6:02 PM
    • Merged by Rudedog2 Wednesday, May 8, 2013 6:39 PM : duplicate post :
    Tuesday, May 7, 2013 6:02 PM
  • List<Task> tasks = new List<Task>();
    
    Task finalTask = Task.FromResult(true);
    foreach (var task in tasks)
    {
        finalTask = finalTask.ContinueWith(t => task.Start());
    }
    This takes each task and adds a continuation that starts the next task in the list.  "finalTask" then represents a task that will be completed when the last task is finished.
    Tuesday, May 7, 2013 6:12 PM
  • Task finalTask = Task.FromResult(true);

    I don't understand this? There's no FromResult in Task.


    Sunil

    Tuesday, May 7, 2013 6:19 PM
  • Then you're using .NET 4.0, not 4.5.  You can write your own "FromResult" quickly enough for 4.0 though:

    public static class TaskExtensions
    {
        public static Task<T> FromResult<T>(T result)
        {
            var tcs = new TaskCompletionSource<T>();
            tcs.SetResult(result);
            return tcs.Task;
        }
    }



    • Edited by servy42 Tuesday, May 7, 2013 6:22 PM
    Tuesday, May 7, 2013 6:22 PM
  • Thank you for quick reply.

    But I have another problem.

    Task finalTask = Task.FromResult(true);
    
                    foreach (var task in allTasks)
                    {
                        finalTask = finalTask.ContinueWith(t => { task.Start(); RefreshGrid(); }, TaskScheduler.FromCurrentSynchronizationContext());
                    }

    Each thread should refresh my UI. But when I use TaskScheduler.FromCurrentSynchronizationContext(), it's blocking my UI until it completes all tasks; since it runs them on the same thread.

    I want to run these tasks independent of my UI thread. And each child task should update the UI on completion.

    Could you please provide me some solution for this?

    Thanks in advance.


    Sunil

    Tuesday, May 7, 2013 6:40 PM
  • You need to have separate continuations:

    Task finalTask = Task.FromResult(true);
    foreach (var task in tasks)
    {
        task.ContinueWith(t => RefreshGrid(), TaskScheduler.FromCurrentSynchronizationContext());
        finalTask = finalTask.ContinueWith(t => task.Start());
    }

    This way the continuation that refreshes the grid runs in the UI context, but the continuation that starts the task does not.

    Tuesday, May 7, 2013 6:47 PM
  • Wow I got the solution. Thank you.

    foreach (var task in allTasks)
                {
                    finalTask = finalTask.ContinueWith(t => { task.Start(); task.Wait(); }).ContinueWith((tas) => ReloadGrid(), TaskScheduler.FromCurrentSynchronizationContext());
                }


    Sunil

    Tuesday, May 7, 2013 6:47 PM
  • You do *not* want to have a call to "Wait" in there.  It will result in a whole bunch of thread pool threads doing nothing but sitting around waiting on other threads.  This is wasting resources unnecessarily.  You should simply be applying the continuation to the appropriate task, as I demonstrated in my answer.
    • Edited by servy42 Tuesday, May 7, 2013 6:52 PM
    Tuesday, May 7, 2013 6:51 PM
  • But my each task should wait for the other task to finish. They should be executed one by one. But they should run on a thread other than my UI thread and report the result to UI thread, upon which UI thread should refresh the result.

    Sunil

    Wednesday, May 8, 2013 4:01 AM
  • I need to invoke an event(which updates UI) before starting the task and invoke an event (which updates UI) after the task is completed.

    If I have list of tasks that are executing one by one, how'll I do this for each task?


    Sunil

    Wednesday, May 8, 2013 6:34 AM
  • But my each task should wait for the other task to finish. They should be executed one by one. But they should run on a thread other than my UI thread and report the result to UI thread, upon which UI thread should refresh the result.
    You don't want to have a task sitting there waiting, you simply want to start one task when the previous one is finished.  You don't need to wait to do that, you can do that asynchronously.  That's the entire purpose of the `ContinueWith` method.  It's entire goal is to do something when a task finishes, but without having a thread sitting there doing nothing waiting until it's done.  That method is what's doing the "waiting" for you, without actually wasting a threads time.  By starting each task as a continuation of the "previous" thread you are ensuring that each one starts after the previous finishes, meaning only one is ever run at a time.
    Wednesday, May 8, 2013 4:39 PM
  • I need to invoke an event(which updates UI) before starting the task and invoke an event (which updates UI) after the task is completed.

    If I have list of tasks that are executing one by one, how'll I do this for each task?


    Sunil

    Exactly, and that is a separate issue from that of starting each task when the previous finishes.  That's why, in my code, it's an entirely separate line, with an entirely separate continuation.  You're doing two completely different things here, so make that clear in your code.  The second line of code inside my loop is starting each task when the previous finishes.  The first is updating the UI when the task completes, which is exactly what you've just said you want to do; you want to update the UI when the task completes.  The code reads just like your description of what you want.  

    Another advantage of my method is that you parallelize the UI and non-UI tasks.  There's no reason to wait until the UI has finished updated to start the next task, so why have it wait on that? 

    Wednesday, May 8, 2013 4:43 PM
  • Thanks for your response.

    No I tried your code. My task does some action and should update the UI and then continue with the next task. Please see below what I wanted to achieve.

    foreach (var task in allTasks){

    //1. inform UI that task started (an event is raised.)

    //2. start the task(runs for around minute.)

    //3. inform UI that task has finished (an event is raised.)

    //4. ContinueWith next task in the list

    }

    I have problem with UI update event. This should run on the UI thread. But the task will run on another thread. I have to run UI tasks on the UI thread, otherwise I'll get cross thread operation exception.


    Sunil

    Wednesday, May 8, 2013 5:09 PM
  • Hi I wanted to achieve something like below.

    My windows form

    void btn_Click()

    {

    TaskHelper ts = new TaskHelper();

    ts.TaskStarted += team_TaskStarted;

    ts.TaskCompleted += team_TaskCompleted;

    ts.RunAllTasks();

    }

    void team_TaskStarted(object sender,EventArgs e){RefreshGrid();}

    void team_TaskCompleted(object sender,EventArgs e){RefreshGrid();}

    pubic Class TaskHelper

    {

    public void RunAllTasks()

    {

    foreach (MyTeam task in List<MyTeam>){

    //1. inform UI that task started (an event is raised.)

    //2. start the task(runs for around minute.)

    //3. inform UI that task has finished (an event is raised.)

    //4. ContinueWith next task in the list

    }

    }



    Sunil

    Wednesday, May 8, 2013 5:17 PM
  • You can call the method synchronously and use Application.DoEvents after updating the UI

    Muthukrishnan Ramasamy
    net4.rmkrishnan.net
    Use only what you need, Reduce global warming


    Wednesday, May 8, 2013 5:25 PM
  • I have problem with UI update event. This should run on the UI thread. But the task will run on another thread. I have to run UI tasks on the UI thread, otherwise I'll get cross thread operation exception.

    Which is why my code runs the UI updating continuation using the current synchronization context but uses the default synchronization context for the task itself, so that exact behavior can be observed.

    Why do you need to run code before the task starts?  Based on your example it looks like you're doing the same thing when the task starts as when one ends, which just means you're updating the UI twice between each task.  Do you really have a compelling need to do this?  (If you do you can just add in a third continuation, just be sure you really need it.)

    • Edited by servy42 Wednesday, May 8, 2013 5:44 PM
    Wednesday, May 8, 2013 5:42 PM
  • You can call the method synchronously and use Application.DoEvents after updating the UI
    Really?  Seriously?  This is *terrible* advice and not at all the appropriate way to address this issue and is almost certainly going to result in buggy, unreliable, and user unfriendly code.
    • Edited by servy42 Wednesday, May 8, 2013 5:56 PM
    Wednesday, May 8, 2013 5:46 PM
  • It's not what I wanted. This no way solves my problem.

    Sunil

    Wednesday, May 8, 2013 5:53 PM
  • Yes, I need it. I am just giving a skeleton of what I needed. But there's lot of things happening in between.

    I tired to use your code. But the tasks are triggered without the other task getting finished. 


    Sunil

    Wednesday, May 8, 2013 6:10 PM
  • public class MultiTasking
        {
            Random rd = new Random();
    
            public event ReloadGridHandler ReloadGrid;
            private EventArgs args;
    
            public void RunAllTasks(List<Employee> lstEm)
            {
                Task[] allTasks = new Task[lstEm.Count()];
                int i = 0;
                foreach (Employee emp in lstEm)
                {
                    allTasks[i++] = new Task(() => RunEmp(emp));
                }
    
                Task finalTask = Task.Factory.StartNew(() => { });
    
                foreach (var task in allTasks)
                {
                    finalTask = finalTask.ContinueWith(t => { task.Start(); }).ContinueWith((tas) => ReloadGrid(task, args), TaskScheduler.FromCurrentSynchronizationContext());
                }
            }
    
            private void RunEmp(Employee emp)
            {
                Thread.Sleep(3000);
                emp.Id = rd.Next(20) * 1000;
    
                RunTask rt = new RunTask();
                rt.TaskCompleted += rt_TaskCompleted;
                rt.RunIt();
            }
    
            void rt_TaskCompleted(object sender, EventArgs e)
            {
                args = new EventArgs();
            }
        }
    
        public delegate void ReloadGridHandler(object sender, EventArgs e);
    
        public class RunTask
        {
            public event TaskCompletedHandler TaskCompleted;
    
            public void RunIt()
            {
                TaskCompleted(this, null);
            }
        }
    
        public delegate void TaskCompletedHandler(object sender, EventArgs e);

     private void TaskSample_Load(object sender, EventArgs e)
            {
                lstEm.Add(new Employee(1, "detg"));
                lstEm.Add(new Employee(2, "b"));
                lstEm.Add(new Employee(3, "a"));
                lstEm.Add(new Employee(4, "c"));
                lstEm.Add(new Employee(5, "rtty"));
                lstEm.Add(new Employee(6, "d"));
                lstEm.Add(new Employee(7, "f"));
                lstEm.Add(new Employee(8, "g"));
                lstEm.Add(new Employee(9, "e"));
    
                dataGridView1.DataSource = lstEm;
            }
    
            private void dataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e)
            {
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                MultiTasking mt = new MultiTasking();
                mt.ReloadGrid += mt_ReloadGrid;
                mt.RunAllTasks(lstEm);
            }
    
            void mt_ReloadGrid(object sender, EventArgs e)
            {
                try
                {
                    label1.Text = "Cleared";
                    dataGridView1.Refresh();
                }
                catch (Exception ex)
                {
                }
            }

    Please see the above test code of mine.


    Sunil

    Wednesday, May 8, 2013 6:11 PM
  • Here the task is not waiting. It's continuing before it finishes.

    Sunil

    Wednesday, May 8, 2013 6:13 PM
  • That's because you're not running the code that I showed you.  You're using the continuation that updates the UI on the task that is the continuation of starting the task.  That's not right.  In my code The continuation to update the UI was applied to the task in the loop variable.  In your case you're starting to update the UI once you've finished starting the task, not when the task has completed.  In my code it updates the UI when the task has actually finished.  It's an important distinction.  It's not like that difference was just there to add in more line breaks.

    Note that the two lines of code inside the loop in my solution don't need to be in the order they are in; you can swap them around if it helps you.

    Wednesday, May 8, 2013 6:17 PM
  • Yes, even I tried that. But it's executing the tasks randomly. i.e. not one by one.

    foreach (var task in allTasks)
                {
                    finalTask = finalTask.ContinueWith(t => { task.Start();});
                    task.ContinueWith((tas) => ReloadGrid(task, args), TaskScheduler.FromCurrentSynchronizationContext());
                }


    Sunil

    Wednesday, May 8, 2013 6:28 PM
  • Right, I went and tested it myself.  I had a minor bug.  I was starting each task when the previous one finished being started, not when it was finished, but the fix is small:

    Task finalTask = Task.FromResult(true);
    foreach (var task in tasks)
    {
        task.ContinueWith(t => RefreshGrid(), TaskScheduler.FromCurrentSynchronizationContext());
        finalTask.ContinueWith(t => task.Start());
        finalTask = task;
    }

    Here's a much simpler program that can demonstrate this:

    public Task ExecuteSerially(IEnumerable<Task> tasks)
    {
        Task finalTask = Task.FromResult(true);
        foreach (var task in tasks)
        {
            finalTask.ContinueWith(t => task.Start());
            finalTask = task;
        }
        return finalTask;
    }
    
    private void button1_Click(object sender, EventArgs args)
    {
        StringBuilder sb = new StringBuilder();
        List<Task> tasks = new List<Task>();
        for (int i = 0; i < 10; i++)
        {
            int taskNumber = i;
            tasks.Add(new Task(() =>
            {
                sb.AppendFormat("Starting task {0}\n", taskNumber);
                Thread.Sleep(1000);//placeholder for real work
                sb.AppendFormat("Finishing task {0}\n", taskNumber);
            }));
        }
    
        ExecuteSerially(tasks).ContinueWith(t =>
        {
            MessageBox.Show(sb.ToString());
        });
    }

    Wednesday, May 8, 2013 6:48 PM
  • No. I tried this too. It's just executing only one task. There's one strange thing also happening. This worked for me in one machine. I'm trying to run the same on a different machine, it's failing.

    Sunil

    Thursday, May 9, 2013 4:59 AM
  • http://blog.stephencleary.com/2010/06/reporting-progress-from-tasks.html

    I found the solution to my problem from the above link.


    Sunil

    Wednesday, May 22, 2013 4:54 AM