INTELLIGENT WORK FORUMS
FOR COMPUTER PROFESSIONALS

Log In

Come Join Us!

Are you a
Computer / IT professional?
Join Tek-Tips Forums!
  • Talk With Other Members
  • Be Notified Of Responses
    To Your Posts
  • Keyword Search
  • One-Click Access To Your
    Favorite Forums
  • Automated Signatures
    On Your Posts
  • Best Of All, It's Free!

*Tek-Tips's functionality depends on members receiving e-mail. By joining you are opting in to receive e-mail.

Posting Guidelines

Promoting, selling, recruiting, coursework and thesis posting is forbidden.

Jobs

System Architecture

Keeping the UI responsive by jmeckley
Posted: 8 Oct 09 (Edited 6 Oct 10)

Keeping the UI responsive.

This FAQ stemmed from thread732-1549246: Prevent Thread Re-entrance on Same Thread and an article by Jeremy Miller on functional programming. The premise of my intrigue was how can I effectively manage the user experience, pushing as much as I can to the background. This seems like a good challenge considering I have never designed a WinForms app:)

I decided on a FAQ because I figure others can benefit from this as well. The solution is generic enough that you can plug this into your applications with minimal impact. This also cuts down on reading through lengthy post and getting to the good stuff.

My first solution utilized ISynchronizeInvoke. I came to this conclusion by following the bread crumbs. I got the Cross Thread exception while attempting to update the GUI from a System.Timer.Elasped event. Following the help links I came across ISynchronizeInvoke. Google provided me with more insight into how to use ISynchronizeInvoke. Refactored to abstract and centralize the logic and this is what I ended up with

CODE

public static class Preform
{
    public static SomthingBuilder ThisAction(Action action)
    {
        return new SomthingBuilder(action);
    }
}

public class SomthingBuilder
{
    private delegate void Now();
    private readonly Action action;

    public SomthingBuilder(Action action)
    {
        this.action = action;
    }

    public void Against(ISynchronizeInvoke control)
    {
        if (control.InvokeRequired)
        {
            control.Invoke(Do.This(action), null);
        }
        else
        {
            action();
        }
    }

    private class Do
    {
        private readonly Action action;

        public static Now This(Action action)
        {
            return new Do(action).execute;
        }

        private Do(Action action)
        {
            this.action = action;
        }

        private void execute()
        {
            action();
        }
    }
}
which can be used like this

CODE --> MyForm.cs

public void AnEventHandlerTriggeredFromABackgroundThread(string sometext)
{
    Preform
        .ThisAction(() => aLable.Text = sometext)
        .Against(this);
}
This worked and I was pleased that I could figure out how to solve cross threading issue, but it still felt burdensome. I would need to do this for every GUI control that ever changed to ensure I was operating on the main thread. some time passed and I continued to read in my spare time when the muses were upon me.

I came across a podcast by Udi (http://www.udidahan.com/) on how to manage the user experience in WinForms. While ISynchronizeInvoke works, it can cause latency if there are a large number of controls that get updated all at once. He recommended BoundContexts. For the life of me I could not wrap my head around this and never did find a solution.

Just a few days ago Jeremy posted an article on MSDN about practical every day uses of functional programming in .Net. One application of using funcational code (delgates) is continueation. In this context it means processing eventually processing the code on another thread and continuing the execution on the mainthread. this is orchestrated by the ThreadPool Queue and the SynchronizatonContext. after a day or two it all came together. It also works well for large amounts of data as the operation is marshaled, not individual controls. This method uses the request/response model. There is also the pub/sub (publish subscribe) model which, is hinted at with the ISynchronizeInvoke example. A full implementation in another FAQ.

The AsynchronousExecutor will delegate the work to the next available thread. if the action returns another action we will marshal this back to the main thread. AsynchronousExecutor is intended for use only at the presentation level.

CODE

public interface ICommandExecutor
{
    void Execute(Action action);
    void Execute(Func<Action> action);
}

public class AsynchronousExecutor : ICommandExecutor
{
    private readonly SynchronizationContext synchronizationContext;

    public AsynchronousExecutor(SynchronizationContext synchronizationContext)
    {
        this.synchronizationContext = synchronizationContext;
    }

    //useful for operations that will not have an impact on the GUI. one-way operations
    public void Execute(Action action)
    {
        ThreadPool.QueueUserWorkItem(item => action());
    }

    //do the bulk of the work in the background, then marshal the result back to the UI thread.
    public void Execute(Func<Action> action)
    {
        ThreadPool.QueueUserWorkItem(item =>
                                         {
                                             var continuation = action();
                                             synchronizationContext.Send(callBack => continuation(), null);
                                         });
    }
}
Execute(Action) is useful for one way operations where the GUI will not be impacted by the changes.
Execute(Fun<Action>) is how we can execute work in the background and then pass the screen updates to the GUI

here is a simple spike to demonstrate how this works.

CODE --> Form

public interface IView
{
    void UpdateUI(string value);
}
public partial class MainForm : Form, IView
{
    private readonly Presenter Presenter;

    public MainForm()
    {
        InitializeComponent();
        //in production this would be handled by an IoC container.
        Presenter = new Presenter(this, new Service(), new AsynchronousExecutor(SynchronizationContext.Current));
    }

    protected override void OnShown(EventArgs e)
    {
        Presenter.UpdateView();
    }

    public void UpdateUI(string value)
    {
        CurrentTimeLabel.Text = value;
    }

    private void button1_Click(object sender, EventArgs e)
    {
        MessageBox.Show("hello", "caption", MessageBoxButtons.OK);
    }
}
the button click is there just to show that the UI is available to the user while we wait for the presenter to update the view.
the following code is a simple MVP implementation.

CODE --> presenter

public class Presenter
{
    private readonly IView View;
    private readonly IService Service;
    private readonly ICommandExecutor Executor;

    public Presenter(IView view, IService service, ICommandExecutor executor)
    {
        View = view;
        Service = service;
        Executor = executor;
    }

    public virtual void UpdateView()
    {
        Executor.Execute(() =>
                             {
                                 //all the work that should happen on the background thread should start here
                                 var text = Service.GetData();
                                 //and end here

                                 //return an action to update the GUI
                                 return () => View.UpdateUI(text);
                             });
    }
}
for the service we introduce a 5 second delay to mimic a long running process.

CODE --> service

public interface IService
{
    string GetData();
}

public class Service : IService
{
    private const int FIVE_SECONDS = 5000;

    public string GetData()
    {
        Thread.Sleep(FIVE_SECONDS);
        return string.Format("the time is {0:F}", DateTime.Now);
    }
}
pretty slick. the bulk of our code is clean, has no knowledge of threading or synchronization and it's very easy to test using automation. My next challenge is implementing a pub/sub architecture for WinForms, instead of request/response.

UPDATE: I finally got around to understanding and creating a event broker for the UI. This solves the pub/sub dilemma I wanted to solve. I posted the code on Google Code.

Back to C# (C sharp): Microsoft FAQ Index
Back to C# (C sharp): Microsoft Forum

My Archive

Resources

Close Box

Join Tek-Tips® Today!

Join your peers on the Internet's largest technical computer professional community.
It's easy to join and it's free.

Here's Why Members Love Tek-Tips Forums:

Register now while it's still free!

Already a member? Close this window and log in.

Join Us             Close