William さんのプロフィール.Net Zoneブログリスト ツール ヘルプ
12月23日

WinForms Tier-Splitting using Volta

 

I love the Volta idea and concept. The cool part is not the features, but lack of them.  It is so easy to create a two tier app it is almost not funny.

Here is a video I did on splitting using a Winforms app.  This uses Silverlight publishing so just double-click on the video to go full screen.

 

12月21日

They are Anonymous Methods, not Anonymous Delegates.

 

It is not just a talking point because we want to be difficult. It helps us reason about what exactly is going on.  To be clear, there is *no such thing as an anonymous delegate. They don't exist (not yet).  They are "Anonymous Methods" - period.  It matters in how we think of them and how we talk about them.  Lets take a look at the anonymous method statement "delegate() {...}".  This is actually two different operations and when we think of it this way, we will never be confused again.  The first thing the compiler does is create the anonymous method under the covers using the inferred delegate signature as the method signature.  It is not correct to say the method is "unnamed" because it does have a name and the compiler assigns it. It is just hidden from normal view.  The next thing it does is create a delegate object of the required type to wrap the method. This is called delegate inference and can be the source of this confusion. For this to work, the compiler must be able to figure out (i.e. infer) what delegate type it will create. It has to be a known concrete type.  Let write some code to see why.

private void MyMethod()
{
}

Does not compile:

1) Delegate d = delegate() { };                       // Cannot convert anonymous method to type 'System.Delegate' because it is not a delegate type
2) Delegate d2 = MyMethod;                         // Cannot convert method group 'MyMethod' to non-delegate type 'System.Delegate'
3) Delegate d3 = (WaitCallback)MyMethod;   // No overload for 'MyMethod' matches delegate 'System.Threading.WaitCallback'

Line 1 does not compile because the compiler can not infer any delegate type. It can plainly see the signature we desire, but there is no concrete delegate type the compiler can see.  It could create an anonymous type of type delegate for us, but it does not work like that.  Line 2 does not compile for a similar reason. Even though the compiler knows the method signature, we are not giving it a delegate type and it is not just going to pick one that would happen to work (not what side effects that could have).  Line 3 does not work because we purposely mismatched the method signature with a delegate having a different signature (as WaitCallback takes and object).

Compiles:

4) Delegate d4 = (MethodInvoker)MyMethod;  // Works because we cast to a delegate type of the same signature.
5) Delegate d5 = (Action)delegate { };              // Works for same reason as d4.
6) Action d6 = MyMethod;                                // Delegate inference at work here. New Action delegate is created and assigned.

In contrast, these work. Line 1 works because we tell the compiler what delegate type to use and they match, so it works.  Line 5 works for the same reason. Note we used the special form of "delegate" without the parens. The compiler infers the method signature from the cast and creates the anonymous method with the same signature as the inferred delegate type. Line 6 works because the MyMethod() and Action use same signature.

I hope this helps.

Also see: http://msdn.microsoft.com/msdnmag/issues/04/05/C20/

12月20日

Simple Task Scheduler inside your application (C#)

 

Many times I need a simple task scheduler inside a program to do this or that. You can use any of the system timers to achieve this, but you end up writing the same difficult code over and over. This simple scheduler is built to be simple and efficient. There is no interval "period" (i.e. system Timer) to worry about or set. If you need to reschedule a Task, just reschedule it at the end of your task delegate. That way, you don't have to worry about strange recursive overlaps. In a perfect world, I just would return a Task (in the pfx library), but you can't schedule a pfx.Task to run in the future. It is started during construction, so I could not return a Task. Instead I created something similar and named it TimerTask.

TimerScheduler is a static class that has 2 simple static methods - Add and Remove.  The Add() method will schedule a delegate (can use lambda syntax) at any point in the future and immediately return a TimerTask object to represent the task. The TimerTask can be canceled if it has not run yet. The Remove() method will un-schedule any task if it has not been started.  The scheduler works similar to System.Threading.Timer in that it adds Tasks to a list sorted by run times. Only one internal thread schedules tasks, each on a thread pool thread. The scheduler thread will block (i.e. do nothing) if no tasks are ready to run or the list is empty so it is efficient.  Any number of tasks can be scheduled to the limits of resources and max list size.  The only resources used for all waiting tasks is the object allocations and the single scheduler thread.

Hope it finds a way in a program or two.

--William

public static class TestTaskScheduler
{
    public static void Test1()
    {
        TimerTask tt = TaskScheduler.Add(DateTime.Now, () =>  Debug.WriteLine("Task1 done."));

        TimerTask tt2 = null;
        tt2 = TaskScheduler.Add(5000, () =>
        {
            Debug.WriteLine("Task2 done.");
            string ss = string.Format("Task:{0} RunDate:{1}", tt2.Completed, tt2.RunDate);
            Debug.WriteLine(ss);
        });
    }
}

public class TimerTask
{
    internal int completed;
    internal Exception exception;
    private readonly Action action;
    private readonly DateTime runDate;

    internal TimerTask(Action action, DateTime runDate)
    {
        this.action = action;
        this.runDate = runDate;
    }

    public bool Completed
    {
        get { return completed == 1; }
    }

    public DateTime RunDate
    {
        get { return this.runDate; }
    }

    public Exception Exception
    {
        get { return this.exception; }
    }

    public bool Cancel()
    {
        return TaskScheduler.Remove(this);
    }

    internal Action Action
    {
        get { return this.action; }
    }
}

/// <summary>
/// Schedules Tasks to run at future time.
/// </summary>
public static class TaskScheduler
{
    /// <summary>
    /// The maximum lag time allowed. RunAt times less then this value are not allowed.
    /// </summary>
    public static readonly TimeSpan MaxPassed;
    private static readonly object sync;
    private static Thread mt;
    private static SortedList<DateTime, TimerTask> sl;

    static TaskScheduler()
    {
        MaxPassed = TimeSpan.FromSeconds(-5);
        sync = new object();
        sl = new SortedList<DateTime, TimerTask>();
        mt = new Thread(Scheduler);
        mt.Name = "NewTimerScheduler";
        mt.IsBackground = true;
        mt.Start();
    }

    /// <summary>
    /// Schedule a Task to run at specified time.
    /// </summary>
    /// <param name="runAt"></param>
    /// <param name="action"></param>
    /// <returns></returns>
    public static TimerTask Add(TimeSpan runAt, Action action)
    {
        DateTime dt = DateTime.Now.Add(runAt);
        return Add(dt, action);
    }

    /// <summary>
    /// Schedule a Task to run at specified time.
    /// </summary>
    /// <param name="milliseconds"></param>
    /// <param name="action"></param>
    /// <returns></returns>
    public static TimerTask Add(double milliseconds, Action action)
    {
        DateTime dt = DateTime.Now.AddMilliseconds(milliseconds);
        return Add(dt, action);
    }

    /// <summary>
    /// Schedule a Task to run at specified time.
    /// </summary>
    /// <param name="runAt"></param>
    /// <param name="action"></param>
    /// <returns></returns>
    public static TimerTask Add(DateTime runAt, Action action)
    {
        if (action == null)
            throw new ArgumentNullException("action");
        if ((runAt - DateTime.Now) < MaxPassed)
            throw new InvalidOperationException("runAT is too far in the past.");

        TimerTask to = new TimerTask(action, runAt);

        lock (sync)
        {
            sl.Add(to.RunDate, to);
            Monitor.Pulse(sync);
            return to;
        }
    }

    /// <summary>
    /// Remove a TimerTask.
    /// </summary>
    /// <param name="timerTask">The TimerTask that was scheduled.</param>
    /// <returns>true if TimerTask has not been scheduled yet; otherwise false.</returns>
    public static bool Remove(TimerTask timerTask)
    {
        if (timerTask == null)
            throw new ArgumentNullException("timerTask");

        lock (sync)
        {
            int index = sl.IndexOfValue(timerTask);
            if (index > -1)
            {
                sl.RemoveAt(index);
                Interlocked.Exchange(ref timerTask.completed, 1);
                return true;
            }
            return false;
        }
    }

    /// <summary>
    /// Main scheduler thread. One thread is used to schedule queued tasks.
    /// Tasks are run on a ThreadPool thread when there start time triggers.
    /// </summary>
    private static void Scheduler()
    {
        lock (sync)
        {
            while (true)
            {
                while (sl.Count == 0)
                    Monitor.Wait(sync);

                TimerTask first = sl.Values[0];

                // If first has been canceled, remove it without running.
                if (first.Completed)
                {
                    sl.RemoveAt(0);
                    continue;
                }

                // If time for first task to run, run it on a ThreadPool thread.
                if (first.RunDate <= DateTime.Now)
                {
                    // Run task on thread pool thread.
                    sl.RemoveAt(0);
                    ThreadPool.QueueUserWorkItem(delegate(object obj)
                        {
                            try
                            {
                                first.Action(); // Exec user task delegate.
                            }
                            catch(Exception ex)
                            {
                                Interlocked.Exchange<Exception>(ref first.exception, ex);
                            }
                            Interlocked.Exchange(ref first.completed, 1);
                        });
                    continue;   // Start over.
                }

                // No task is ready to run, so wait for timespan.
                TimeSpan waitTill = first.RunDate - DateTime.Now;
                if (waitTill.Milliseconds <= 0)
                    continue;   // Run it now.
                Monitor.Wait(sync, waitTill);
            }
        }
    }
}