DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
11 Monitoring and Observability Tools for 2023
Learn more

A Monad in C# for Simplifying WPF Multi-Threading for a More Responsive GUI

Sadek Drobi user avatar by
Sadek Drobi
·
Jun. 25, 12 · Interview
Like (0)
Save
Tweet
Share
5.27K Views

Join the DZone community and get the full member experience.

Join For Free

Code included here is over simplified for clarity, I hosted a better implementation code on CodePlex. These modifications change strictly nothing for the client code and are only an implementation detail. I use a continuation rather than a delay, and I chose to design a custom continuation class rather than using a delegate because of a type system limitations.

DSC_2468Most GUI frameworks, including Silverlight and WPF, are shipped with a fundamental problem: long use of the main thread causes the Window to blackout, and using different threads requires you to get your hands dirty with the Dispatcher stuff and freezable objects. Worse, you wont learn the necessity to do so until you get a surprise of “The calling thread cannot access this object because a different thread owns it.” exception when all what you were doing is to use available methods on an object that seemed you have access to, at lease it seemed until runtime! This post illustrates a solution based on Monads abstraction and LinQ syntax.

This is a problem you get often when applying Model View Presenter pattern [MVP]. There, your view (which is a WPF control) implements a contract IView that the presenter in its turn will use to extract values and then make operations on the view.

 

interface ISayHello { string GetName(); Unit SayHello(string name); }

class MyLoginControl:Control,ISayHello {

//this interface gets implemented by the corresponding control as texboxes and a text area…

}

The problem appears when for doing any realistic responsive application, the presenter (representing business operations if you’d like) will have to run on a background thread to leave the main thread free for graphics rendering.

While working on a background thread, the presenter needs to access the view (having a reference to it through a contract) and there something wrong happen “The calling thread cannot access this object because a different thread owns it.” . 

The problem here is simply that the view is giving quite a promise that it simply can not satisfy which is implementing the IView contract.

The view cannot satisfy the ISayHello contract under all circumstances, and not even under a commonly desirable condition (the presenter or business code running on another thread). This fact is simply not communicated through the type.

The solution I suggest to this problem that I implemented and employed in a production real world project is based on the LinQ syntax added to C# last year. In the solution I use two things: an extension method and a special type.

The type that I use is the monadic type (thanks to Wesdyer for his enlightening posts): View<T>

So in my case my type will be View<IView> which means that what I am offering here is a a type that acts under some special circumstances as the Contract ISayHello . If I want for example to extract the name from the WPF control, I need to do something with the View<ISayHello>. 

Using the View<T> Monad:

And here comes the LinQ syntax for help. Having a reference to View<ISayHello>, the only way with which I can access the desired value or methods is using Linq:

Having the view contract:

   public interface ISayHello

        {

            Unit SayHello(string Name);

            string GetName();

        }  

I can extract a view monad that I can pass to the presenter as View<ISayHello> 

   View<ISayHello> view = this.AsView<Window1, ISayHello>(); 

 

note the type View<ISayHello> which is somehow useless without the LinQ syntax:

   (from v in view

    let name = v.GetName()

    select v.SayHello(name)).Do();

 

and you can also use several contracts in the same expression:

   from v in view1

   from v2 in view2

 

 

Implementation of the View<T> Monad:

 

View<T> is not really special. It is just a Delay<T> which is a Func<T>. And the Select implementation is the same one for functions and is not special at all:

   public delegate R View<R>();

   public enum Unit

       {

           Unit

       }

       public static class WPFMonadExtensions

       {

           public static View<U> SelectMany<T, U>(this View<T> m, Func<T, View<U>> k)

           {

               return () => k(m())();

           }

           public static View<U> Select<T, U>(this View<T> m, Func<T, U> selector)

           {

               return () => selector(m());

           }

           public static View<V> SelectMany<T, U, V>(this View<T> source, Func<T, View<U>> kSelector, Func<T, U, V> resultSelector)

           {

               return () =>

                          {

                              var t = source();

                              var u = kSelector(t)();

                              return resultSelector(t, u);

                          };

           }

           public static Unit Do(this View<Unit> k)

           {

               return k();

           }

The fact that I am defining a new delegate type here (View<T>) is because we don’t have type synonyms is C#. And because of this I had to reimplement all the Select methods for this type. Of course all of that Monad plumping code is invisible and all the user needs to do is use the LinQ syntax.

The only specific part about the View monad, is the way you extract it. For Wpf for example the .AsView implementation looks like:

   public static View<TView> AsView<TWPF, TView>(this TWPF value) where TWPF : UIElement, TView

           {

               return value.ToWpfMonad<TWPF, TView>();

           }

    

           public static View<Answer> ToWpfMonad<T, Answer>(this T value)

              where T : UIElement, Answer

           {

               return () =>

               {

                   Answer a = default(Answer);

                   value.Dispatcher.Invoke(DispatcherPriority.Normal, (EventHandler)((sender, e) =>

                   {

                       a = value;

                       if (a is Freezable)

                       {

                           var result = ((Freezable)(object)a).Clone();

                           a = (Answer)(object)result;

                           result.Freeze();

                       }

                   }), null, null);

  

                   return a;

               };

           }

Which merely tells about how to execute the delay when applied.

This is the code responsible for calling on WPF windows using dispatcher and other plumping details. Again code here is simplified for clarity. In the same way one can implement an AsView extension method for Silverlight with no need to change the Select implementation. View<T> is a generic monad and contain nothing specific to GUI technology.

 

Conclusion

The Monad generalization provides a good solution for WPF/Silverlight thread problem. The solution is barely about communicating through the type system the fact that WPF/Silverlight controls are special, and using LinQ expression to operate on them leaving the plumping (Dispatcher.Invoke, Freezable) to the monad library implementers. Also this frees the caller from thread logic that is specific to the implementation technology.

In my project I didn’t work much on the freezing/unfreezing of Wpf controls (copy them and extract them to other threads) as it actually wasn’t necessary for my project. However, I think that a proper implementation of .AsView for WPF/Silverlight that manages Freezable and nested Freezable objects copying would be very interesting and would complete the API.

PS: I decided to finish this draft quickly as I am not having enough time to finish properly. Please tolerate typos and don’t hesitate to ask questions.

Windows Presentation Foundation Monad (functional programming) csharp

Published at DZone with permission of Sadek Drobi. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Collaborative Development of New Features With Microservices
  • Getting a Private SSL Certificate Free of Cost
  • Low-Code Development: The Future of Software Development
  • How To Use Linux Containers

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: