Sunday, June 27, 2010

Thread-safe dynamic control




In C# 4 (or .NET 4 for that matter) we have dynamic dispatch capability baked in the framework. It not only brings us the dynamic invoking capability, it also allows us to design an object that respond to event.

In the code below, we used DynamicObject to make setting/getting of control's property and calling its method thread-safe. Not only we are able to avoid using extension method(e.g. SetControlPropertyThreadSafe) for accessing control's property and method altogether, we also achieved keeping the original code with minimal modification.




using System;
using System.Windows.Forms;
using System.Dynamic;
using System.Reflection;
using System.Threading;

namespace WinformDynamicThreadsafe
{
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }


    private void button1_Click(object sender, EventArgs e)
    {
            
        ThreadPool.QueueUserWorkItem(o =>
            {
                button2.ThreadSafe().Text = "Hello";
                button2.ThreadSafe().SendToBack();
            });
    }


    private void button2_Click(object sender, EventArgs e)
    {
        ThreadPool.QueueUserWorkItem(o =>
            {
                dynamic tsButton2 = new ThreadSafeDynamicControl(button2);
                tsButton2.Text = "Yeah";
                tsButton2.BringToFront();
            });
    }

    private void button3_Click(object sender, EventArgs e)
    {
        ThreadPool.QueueUserWorkItem(o =>
            {
                button2.Text = "Won't work";
            });
    }
}

       
public class ThreadSafeDynamicControl : DynamicObject, IDisposable
{
    Control _o;

    public ThreadSafeDynamicControl(Control o)
    {
        _o = o;
    }
        
    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        result = _o.CallControlMethodThreadSafe(binder.Name, args);                        
        return true;           
    }


    public override bool TryGetMember(
        GetMemberBinder binder, out object result)
    {
        result = _o.GetControlPropertyThreadSafe(binder.Name);
        return true;
    }

    public override bool TrySetMember(
        SetMemberBinder binder, object value)
    {                        
        _o.SetControlPropertyThreadSafe(binder.Name, value);
        return true;
    }


    void IDisposable.Dispose()
    {
        // throw new NotImplementedException();
    }
}


internal static class Helper
{
    private delegate void SetControlPropertyThreadSafeDelegate(Control control, string propertyName, object propertyValue);
    internal static void SetControlPropertyThreadSafe(this Control control, string propertyName, object propertyValue)
    {
        if (control.InvokeRequired)
        {
            control.Invoke(new SetControlPropertyThreadSafeDelegate(SetControlPropertyThreadSafe), 
                new object[] { control, propertyName, propertyValue });
        }
        else
        {                
            control.GetType().InvokeMember(
                propertyName, BindingFlags.SetProperty, null, control, new object[] { propertyValue });
        }
    }


    private delegate object GetControlPropertyThreadSafeDelegate(Control control, string propertyName);
    internal static object GetControlPropertyThreadSafe(this Control control, string propertyName)
    {
        if (control.InvokeRequired)
            return control.Invoke(
                new GetControlPropertyThreadSafeDelegate(GetControlPropertyThreadSafe),
                new object[] { control, propertyName });
        else
            return control.GetType().InvokeMember(
                propertyName, BindingFlags.GetProperty, null, control, new object[] {});
    }

    private delegate object CallControlMethodThreadSafeDelegate(Control control, string methodName, params object[] values);        
    internal static object CallControlMethodThreadSafe(this Control control, string methodName, params object[] values)
    {
        if (control.InvokeRequired)
            return control.Invoke(
                new CallControlMethodThreadSafeDelegate(CallControlMethodThreadSafe), 
                new object[] { control, methodName, values });            
        else
            return control.GetType().InvokeMember(
                methodName, BindingFlags.InvokeMethod, null, control, values);

    }

    public static dynamic ThreadSafe(this Control o)
    {
        return new ThreadSafeDynamicControl(o);
    }
}
}


No comments:

Post a Comment