Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations SkipVought on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Keeping the UI responsive

System Architecture

Keeping the UI responsive

by  jmeckley  Posted    (Edited  )
Keeping the UI responsive.

This FAQ stemmed from thread732-1549246 and an article by Jeremy Miller on [link http://msdn.microsoft.com/en-us/magazine/ee309512.aspx]functional programming[/link]. 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);
}
[/code]
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;
    }

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

    [color green]//do the bulk of the work in the background, then marshal the result back to the UI thread.[/color]
    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();
[color green]//in production this would be handled by an IoC container.[/color]
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);
}
}
[/code]
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(() =>
{
[color green]//all the work that should happen on the background thread should start here[/color]
var text = Service.GetData();
[color green]//and end here[/color]

[color green]//return an action to update the GUI[/color]
return () => View.UpdateUI(text);
});
}
}[/code]
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);
}
}
[/code]
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 [link http://code.google.com/p/castle-windsor-event-broker/]Google Code[/link].
Register to rate this FAQ  : BAD 1 2 3 4 5 6 7 8 9 10 GOOD
Please Note: 1 is Bad, 10 is Good :-)

Part and Inventory Search

Back
Top