C# have a seperate draw thread for the application.

Discussion in 'Programming' started by elkinm, Aug 10, 2010.

  1. elkinm

    elkinm Platinum Member

    Joined:
    Jun 9, 2001
    Messages:
    2,146
    Likes Received:
    0
    I noticed a while ago that form applications are very slow to draw. When I minimize/ maximize or go over it with another window, I can see the form components redrawn and/or I can erase the form for a second or so.
    I was told it is because the same thread draws and does everything else, the drawing is slow. I was also told if I make the main thread to nothing but draw and another thread do everything else, the drawing would be fast without affecting my application performance.

    I tried usning worked threads based on this page: http://www.daveoncsharp.com/2009/09/create-a-worker-thread-for-your-windows-form-in-csharp/

    This is my code:
    Code:
    public partial class Form1 : Form
        {
            private Thread workerThread = null;
    
            private bool stopProcess = false;
            private delegate void UpdateStatusDelegate();
            private UpdateStatusDelegate updatedtatusdelegate = null;
    
    
            public Form1()
            {
                InitializeComponent();
                this.updatedtatusdelegate = new UpdateStatusDelegate(this.UpdateStatus);
            }
    
            private void UpdateStatus()
            {
                this.textBox1.Text += "*";
            }
    
            private void HeavyOperation()
            {
                for (int i = 0; i < 10000000; i++)
                {  // POSSIBLE BREAK POINT HERE
                    if (!this.stopProcess)
                    {
                        this.Invoke(this.updatedtatusdelegate);
                    }
                    else
                    {
                        this.workerThread.Abort();
                    }
                    if (i == 100000|| i == 9000000)
                    {  //BREAK POINT HERE
                    }
                }
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                this.stopProcess = false;
    
                this.workerThread = new Thread(new ThreadStart(this.HeavyOperation));
                this.workerThread.Start();
            }
    
            private void button2_Click(object sender, EventArgs e)
            {
                this.stopProcess = true;
            }
        }
    
    This is not quite what I had in mind. First of all, the code as is runs very slowly, posibly hours because updating the main delegate and having it draw the extra * takes forever, exactly what I need to avoid.
    If you comment out the first if else in the heavyworker you will notice that the entire 10000000 iterations takes less than a second, so simply updating the main thread and having it draw slows it down and absurd amount of times.

    First, is there a way to have it update, but only draw to the screen maybe once a second or so like the Application.ScreenUpdating = False in excel VBA so it will run fast and still draw to the screen regularly.

    Also, if you have a break point in the break point or possible break point spots in the code and it is stopped, the window stops drawing. The whole point of this is that I want drawing to be completely separate from the underlying operation, so it would continue drawing what was last on the screen even if I am debugging, or the application busy.

    I know it can be done as I have seen .NET applications which were very fast without the mentioned drawing issues or delays and without slowing down the application.

    Can anybody help me modify this basic code to run like I want to?

    Also, this is less important, but I would like the text box to show the number of the current iteration, not a series of stars so I need to send the int i to the delegate which I have also been trying to do.

    Thanks,
    elkinm.
     
    #1 elkinm, Aug 10, 2010
    Last edited: Aug 10, 2010
  2. Loading...

    Similar Threads - seperate draw thread Forum Date
    C# multi-threading problem part two Programming Apr 22, 2017
    Right code for 16 threads Programming Mar 3, 2017

  3. nickbits

    nickbits Diamond Member

    Joined:
    Mar 10, 2008
    Messages:
    4,125
    Likes Received:
    0
    don't call the delegate on each iteration
     
  4. elkinm

    elkinm Platinum Member

    Joined:
    Jun 9, 2001
    Messages:
    2,146
    Likes Received:
    0
    That is true, but by the underlying reason I need this, I need to know where the worker is at all times, I just don't need to draw it. Also, another major concern is that I need the main window to continue to be drawn if I have break point or pause in the worker thread which also does not happen.

    Thanks again.
     
    #3 elkinm, Aug 10, 2010
    Last edited: Aug 10, 2010
  5. nickbits

    nickbits Diamond Member

    Joined:
    Mar 10, 2008
    Messages:
    4,125
    Likes Received:
    0
    just use class variable(s) and protect them with lock() { } statements.

    When a breakpoint is encountered all threads are paused, I don't think there is any way around that, unless you spawn a separate process for your "work".
     
  6. Cogman

    Cogman Lifer

    Joined:
    Sep 19, 2000
    Messages:
    10,129
    Likes Received:
    19
    Yep, the use of a delegate in this situation is VERY inefficient. A much more efficient method would be to output to a shared string in the class and have the GUI grab the string and display it after each refresh. Right now, you are essentially waiting for each refresh to process the next loop.
     
  7. elkinm

    elkinm Platinum Member

    Joined:
    Jun 9, 2001
    Messages:
    2,146
    Likes Received:
    0
    Sorry, if I sound stupid, but I don't know what you mean by class variables (just global class variables) and lock statements?

    I understand that breaking may pause all threads, what I need to see what the windows was displaying, I am short on screen space and when a break point is reached VS goes over the program and it is grayed out. I need to see the last visual state of the application before the break point. Once again, I have seen it done so I know it can be done, I just don't know how.

    Thanks
     
  8. ObscureCaucasian

    ObscureCaucasian Diamond Member

    Joined:
    Jul 23, 2006
    Messages:
    3,934
    Likes Received:
    0
    Also you can turn on double buffering, which would draw the entire form on one window and then swap it all at once as opposed to drawing on the same canvas that is on the screen. Sounds like it's slow enough he'll have "frame-rate" issues though.
     
  9. elkinm

    elkinm Platinum Member

    Joined:
    Jun 9, 2001
    Messages:
    2,146
    Likes Received:
    0
    Do you mean keep a variable in the class and update the variable in that class without calling the delegate?

    I will try that, however, how will the update class know that the variable has changed and needs to draw it?

    Also, in this case everything is in the single form class. What if I have multiple classes and multiple custom forms, the calculation is fast, but I still need all the drawing to be efficient and only done in the main thread.

    Thanks again.
     
  10. ObscureCaucasian

    ObscureCaucasian Diamond Member

    Joined:
    Jul 23, 2006
    Messages:
    3,934
    Likes Received:
    0
    I think he's getting at something like:

    Code:
    String s = "";
     
    ...
    
    private void HeavyOperation()
    {
        for (int i = 0; i < 10000000; i++)
        {  // POSSIBLE BREAK POINT HERE
            if (!this.stopProcess)
            {
                s += "*";
            }
            else
            {
                this.workerThread.Abort();
            }
            if (i == 100000|| i == 9000000)
            {  //BREAK POINT HERE
            }
        }
    }
    
    ...
    
    public override void OnPaint(PaintEventArgs e)  // This signature might be wrong...
    {
        this.textBox1.Text = s;
        base.OnPaint(e);
    }
    
     
    #9 ObscureCaucasian, Aug 10, 2010
    Last edited: Aug 10, 2010
  11. Cogman

    Cogman Lifer

    Joined:
    Sep 19, 2000
    Messages:
    10,129
    Likes Received:
    19
    Code:
    public partial class Form1 : Form
        {
            private Thread workerThread = null;
            private string _statusString = "";
            private string statusString
            {
               get
               {
                   lock(this)
                   {
                      return _statusString;
                   }
               }
               set
               {
                  lock(this)
                  {
                      _statusString = value;
                  }
               }
            }
            private bool stopProcess = false;
            
    
            public Form1()
            {
                InitializeComponent();
            }
    
            private void HeavyOperation()
            {
                for (int i = 0; i < 10000000; i++)
                {  // POSSIBLE BREAK POINT HERE
                    if (!this.stopProcess)
                    {
                        statusString += '*';
                    }
                    else
                    {
                        this.workerThread.Abort();
                    }
                    if (i == 100000|| i == 9000000)
                    {  //BREAK POINT HERE
                    }
                }
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                this.stopProcess = false;
    
                this.workerThread = new Thread(new ThreadStart(this.HeavyOperation));
                this.workerThread.Start();
                while (workerThread.IsAlive)
                {
                   this.textBox1.Text = statusString;
                   Application.DoEvents(); 
                }
            }
    
            private void button2_Click(object sender, EventArgs e)
            {
                this.stopProcess = true;
            }
        }
    
    This is essentially what I mean.
     
  12. nickbits

    nickbits Diamond Member

    Joined:
    Mar 10, 2008
    Messages:
    4,125
    Likes Received:
    0
    Use a timer to update the display.
     
  13. Cogman

    Cogman Lifer

    Joined:
    Sep 19, 2000
    Messages:
    10,129
    Likes Received:
    19
    :) no timer necessary if you do it like I did.
     
  14. nickbits

    nickbits Diamond Member

    Joined:
    Mar 10, 2008
    Messages:
    4,125
    Likes Received:
    0
    True but I doubt the performance will be much better than what he already had. Also DoEvents is to be avoided. I wasn't aware it still existed in WinForms :S. Guess they had to appease the VB6 guys.
     
  15. elkinm

    elkinm Platinum Member

    Joined:
    Jun 9, 2001
    Messages:
    2,146
    Likes Received:
    0
    Double buffering is a good idea and it does help the application show up all at once, however, maybe I am just seeing things, but it still seems to be a little sluggish, possibly because the drawing process still takes to long before it does the swap. Instead of seeing the controls show up one by one or line by line the whole thing shows up at once, but it only shows up after everything is drawn so I think it seems even slower.

    The ultimate goal is still to have all the calculations separate updating values in the form as needed, and sending them to the main thread and then the main thread simply draws them quickly and efficiently on it's own.
     
  16. Cogman

    Cogman Lifer

    Joined:
    Sep 19, 2000
    Messages:
    10,129
    Likes Received:
    19
    It isn't about performance, my method terminates automatically without causing issues. A timer method would be prone to non-termination issues.

    As for the DoEvents function... I don't really see why it is that big of an issue. If you want to update a form in the middle of a complex loop, it works well.
     
  17. ObscureCaucasian

    ObscureCaucasian Diamond Member

    Joined:
    Jul 23, 2006
    Messages:
    3,934
    Likes Received:
    0
    Could you elaborate on this by chance?
     
  18. Cogman

    Cogman Lifer

    Joined:
    Sep 19, 2000
    Messages:
    10,129
    Likes Received:
    19
    Certainly. If you set a timer and the thread finishes before the timer does, you have to specifically terminate the time. It could be especially troubling if you have a timer that sets itself up on termination. While this might not be too big of an issue here, in other situations, tracking and stopping update timers can be a real pain.

    The method I proposed is guaranteed to terminate when the thread finishes executing. It doesn't use timers, it simply frequently updates the text box and uses "DoEvents" to ensure that the screen gets redrawn before doing the next update. It might suffer an Off-by-one error, however, that would be easily fixed by updating the textbox afterwords.

    If you are really paranoid about using DoEvents, then you could spawn a new thread that does essentially the same thing without the "doEvents" Though, I would suggest throwing in a yield to avoid wasteful Cycle consumption.
     
  19. Markbnj

    Markbnj Elite Member <br>Moderator Emeritus
    Moderator

    Joined:
    Sep 16, 2005
    Messages:
    15,688
    Likes Received:
    11
    In general if you create a thread and run it in a tight loop as in your test case, then it's going to want 100 percent of one core. If you have a single core machine that is going to leave little time for the other application threads. Even if you have multiple cores it will generally run more slowly than, say, a more typical scenario where a thread responds to an event, does some work, and exits. Priority plays into it as well. Windows scheduling is an arcane topic that I don't understand well, but I believe that there is some overall budget for a given process at a given priority, and if you create one thread that is constantly running full speed then that has to impact the time alloted to the other threads in the process.

    Unless you have some complex requirement that you haven't elaborated on yet, I would stick with the normal approach, i.e. create a worker thread to manipulate your model classes, leave the main thread alone to manage the UI updates, create an event on the model class that the UI can subscribe to that reflects when an update occurs. You can use a couple of different techniques to make sure the event occurs on the UI thread, including InvokeRequired/BeginInvoke, and the Event-based async pattern. See the posts on this Stack Overflow thread as an entry point:

    http://stackoverflow.com/questions/6184/how-do-i-make-event-callbacks-into-my-win-forms-thread-safe

    The basic flow looks like:

    1) UI subscribes to StuffChanged event on model class
    2) initiating event occurs on UI thread
    3) worker thread spawned and begins working
    4) UI thread event handler returns
    5) worker thread changes some data
    6) worker thread raises event on UI thread (using techniques just discussed)
    7) event handler registered in (1) is entered on the UI thread
    8) UI updates control properties from the model class
    9) UI thread exits event handler
    10) worker thread either continues working or has exited

    I would not use timers, or any design that requires the UI thread to loop and check a state flag. The whole winforms model depends on your doing something brief with the UI thread in response to an event, and then letting it go.

    Note that in many real world scenarios you don't need ongoing updates from the worker thread to the UI thread. In these cases the flow is spawn worker, wait for it to finish, update UI from model. That probably covers 80 percent of the cases we usually encounter, and it's a lot easier as BackgroundWorker already has the internal plumbing to fire RunWorkerCompleted on the creating thread.
     
    #18 Markbnj, Aug 10, 2010
    Last edited: Aug 10, 2010
  20. elkinm

    elkinm Platinum Member

    Joined:
    Jun 9, 2001
    Messages:
    2,146
    Likes Received:
    0
    Sorry, I had to do something for a while.

    I would like to clarify what exactly I need, the implementation is not really important, just that I can understand how it works and how to add it to my code.

    As a basis for most of my coding lets start with Excel and VBA.
    If I make an iteration to go from 0 to 10000 (to start) or the same 1000000 and write the result to cell A1.
    If I just run it, I can see the number increasing in the cell and it takes a while to complete.
    Now I add Application.ScreenUpdating = False fefore the loop and Application.ScreenUpdating = true after. Now when I run the macro, for 10000 the result shows up almost instantly, for 10000000 it takes much longer, a few minutes, but is still much faster then if screen updating was on.
    And the kicker, when it is running, Excel is basically frozen as it is single threaded, however, if I drag the window around or drag a window over it I still see all the Excel contents, it does not turn white until the calculation is finished. This is a big part of what I want, drawing independent of the application. (Excel can loose drawing in many cases but it is still quite resilient and I want my app to be at least as resilient.

    As far as the actual application. I have a basic form which contains a TableLayoutPanel (10 x 10 or more) and each cell of the layout panel contains a custom controls and that control has a stop and start button as the application so far and a text box showing the current loop counter.
    In the main form there are start and stop buttons which start and stop all the counters at once. Each counter can be started or stoped seperately but the buttons in that control.

    The goal, the GUI must be robust, I cannot erase it or visually see it be slow if I minimize or maximize it or drag a window over it. Each control must be aware of the counter state it is on and update it's text box, and the main application should be able to query the counter of any control at any time, however, there is no need to display all the numbers in each form to the user and the displayed number can be behind.
    The application itself must also be fast, ideally as fast as the loop was when it was not updating the main display.

    My though was to have the draw thread or something and it draws what is needed, just Like the Excel window even when running the macro. Excel with screen updating draws every single update, I could enable and disable screen updating every 1000 or so updates which would still be much faster and give me periodic updates, but I don't think C# requires the same approach. As far as I understand, as long as all variable are local and nothing is drawn to the screen, there is no reason why all the loops should complete almost instantly I can have a global integer and update it with i every time and the loop finishes just as fast, then I can display the final value.

    In the event the thread takes longer or is slower then I would like to see periodic updates on the screen, as I mentioned, the displayed value can be way off from actual, but internally the program knows where it is at any time and react accordingly.

    Once again, the application must be very fast, look fast and be responsive to button clicks.

    I thought multi-threading was the solution but if there is a better way I am all for it.

    Sorry for being such a pain. I am still trying to figure out all the previous posts.

    Thanks again everyone, hopefully this helps a little.
     
  21. Cogman

    Cogman Lifer

    Joined:
    Sep 19, 2000
    Messages:
    10,129
    Likes Received:
    19
    The key to good multithreading is keeping threads as separate as possible. The more threads have to communicate with each other, the bigger the slowdown you will see. Now, with windows, you pretty much are bound to have one thread handle all gui stuff (I don't know if it is impossible, though I have looked, but as far as I can tell only the thread that created a windows resource can access it. IE a DC).

    After that, to make things feel fast, you'll want to have background threads do all the work, and then update when the thread is done. To keep things fluid, you'll want to avoid getting status updates from the thread.
     
  22. elkinm

    elkinm Platinum Member

    Joined:
    Jun 9, 2001
    Messages:
    2,146
    Likes Received:
    0
    To add a little, I just changed the loop to set a global variable to val and call the delegate which updates the text box. I noticed that the updates to the screen are slower this way then with Excel.

    Then I simply changed the delegate to not update the textbox until the high values and it is very fast, at least up until then.

    Now I could have the delegate itself only update in increments, and have the worker run full speed, but I still think it is to involving and I don't want the worker to stop or slow down when I am updating the screen.

    I tried adding the OnPaint event as mentioned earlier, but I got a compile error at buildtime but not before saying cannot change access modifiers when overriding protected inherited member ...
    The strange part is that if I block comment out the method, the error remains and OnPaint remains highlighted until I restart VS. Any ideas why this is?
     
  23. elkinm

    elkinm Platinum Member

    Joined:
    Jun 9, 2001
    Messages:
    2,146
    Likes Received:
    0

    That is what I am trying to do have one, the main thread do all the drawing. While all calculation and updating is done in another thread. What I am worried about is since I create different forms in the TableLayoutPanel, I need to create all of these in the main thread before having worker threads, I also need to have the other thread handle events between all classes and I don't know how to create one worker thread for it all.

    Something entirely different. I recently started using tables and DataGridViews, so I am going to try instead of a textbox, simply have a grid and a table with only one cell and set the data source. I am hoping it should be fast.
    At the same time if VS can make this link fast then I don't see why I can't do the same with my one code.
     
  24. Cogman

    Cogman Lifer

    Joined:
    Sep 19, 2000
    Messages:
    10,129
    Likes Received:
    19
    So long as you aren't overdoing it (IE create a thread for 1 + 1), threads are relatively cheap to make. Just make a new thread when you need it, don't worry about having one worker thread for everything.

    However, if you really, really, care about performance, and what to do a lot of little tasks threaded. Then the C# threadpool might be what you are looking for
    http://msdn.microsoft.com/en-us/library/3dasc8as(VS.80).aspx
     
  25. elkinm

    elkinm Platinum Member

    Joined:
    Jun 9, 2001
    Messages:
    2,146
    Likes Received:
    0
    I will take a look at it.

    I did try the gridview method and it was just as slow as the textbox, I was hoping grid updates would be properly throttled.

    So it seems it still updates the table and then has to wait for it to be drawn.

    So I think what I really need is simply to somehow make drawing completely independent, like out of order execution, I send an update to the table or variable, but instead of drawing it right away it waits until it is supposed to, possibly a screen refresh. The value can be updated before the screen refresh so when the screen does refresh the latest known value is displayed.
     
  26. elkinm

    elkinm Platinum Member

    Joined:
    Jun 9, 2001
    Messages:
    2,146
    Likes Received:
    0
    I tried the same code on my home PC with VS 2010. At work I have 2008.
    Anyway, the screen updating is much faster but still very slow. However, on my work PC the invoke operations have little to no effect on performance at least when nothing is updated on the screen, but on my PC the is a significant slowdown.

    I will try saving the time the last time the view was updated and not update for 1 or so seconds which should reduce the update frequency however it is still not very efficient and I miss the last update.
    For the data table I need to try setting the source to null and setting to a value only when needed to see if that helps.

    The problem is that all these methods only work around the problem. The key is have drawing and screen updating slowed than the code updates values. I need to skip values on the screen as frames with video playback.