« September 2008 | Main | November 2008 »

October 26, 2008

Concurrency Pre-Con Highlights

I attended the "Concurrent, Multi-Core Programming on .NET and Windows" pre-conference talk at PDC. It was primarily a summary of the current state of concurrent programming on Windows (native and managed), with a preview of future advances in the platform.

David Callahan, a distinguished engineer at Microsoft, presented first, gave a summary of the "free lunch" being over and what the Parallel Computing Platform team at Microsoft is doing to address that problem. Their goal is to allow developers to easily express latent parallelism in their code, so that when manycore systems are available it can be turned into actual parallelism, giving software a different type of "free lunch". One aspect of this long-term goal is to "eliminate multi-threading"; that is, removing explicit thread management by providing better abstractions on which to build concurrent applications. One takeaway was that even though we currently only have dual- or quad-core systems, we should overdecompose problems now to gain scalability in the future.

Stephen Toub and Joe Duffy presented sessions on the mechanisms that exist for concurrency right now (with quite a number of live demos of threads, thread pool, APM, BackgroundWorker, etc.), best practices for using those mechanisms (lock hierarchies, granularity, etc.), how you can write very low-level code (lock-free, memory barriers), and why you shouldn't do that, but just use the types built into the Framework (and more are coming in .NET 4.0).

Some interesting points from these sessions were:

Lastly, they covered improvements that will be coming in .NET 4.0. Essentially, the Parallel Extensions are becoming part of the core framework--they'll even be integrated into mscorlib.dll, etc. instead of being supplied in a separate assembly (as in the CTPs). The TPL's task scheduler will be baked into the standard ThreadPool so that there's one master scheduler that controls all the background work for the process. PLINQ will, of course, be supported and, like all these new features, available to all .NET languages; there are no new language extensions or compiler changes required. New data structures (ConcurrentQueue, ConcurrentDictionary, etc.) will be supplied in a new System.Collections.Concurrent namespace; there will be new locks (SpinLock, ManualResetEventSlim, SemaphoreSlim, etc.) and other helper classes (blocking collections, CountdownEvent, etc.). Additionally, VC++ 10 is getting its own native concurrency libraries and task scheduler. More details on all of these items will be available at various "Deep Dive" talks later in the week.

Posted by Bradley Grainger at 9:52 PM | Comments (0) | TrackBack

October 22, 2008

How to Reverse a Unicode String in C#

Perhaps due to the lack of a built-in String.Reverse method in the .NET Framework, it's very common (1, 2, 3, 4, 5, 6, 7) for implementations of such a method to be posted.

Unfortunately, most of these implementations do not handle characters outside Unicode's Basic Multilingual Plane correctly. These supplementary characters have code points between U+10000 and U+10FFFF and so cannot be represented with one 16-bit char. In UTF-16 (which is how .NET strings are encoded), these Unicode characters are represented as two C# chars, a high surrogate followed by a low surrogate. When the string is reversed, the order of these two chars has to be preserved.

Here's our method that reverses a string while handling surrogate code units correctly:

/// <summary>

/// Reverses the specified string.

/// </summary>

/// <param name="input">The string to reverse.</param>

/// <returns>The input string, reversed.</returns>

/// <remarks>This method correctly reverses strings containing supplementary characters

/// (which are encoded with two surrogate code units).</remarks>

public static string Reverse(this string input)

{

    if (input == null)

        throw new ArgumentNullException("input");

 

    // allocate a buffer to hold the output

    char[] output = new char[input.Length];

    for (int outputIndex = 0, inputIndex = input.Length - 1; outputIndex < input.Length; outputIndex++, inputIndex--)

    {

        // check for surrogate pair

        if (input[inputIndex] >= 0xDC00 && input[inputIndex] <= 0xDFFF &&

            inputIndex > 0 && input[inputIndex - 1] >= 0xD800 && input[inputIndex - 1] <= 0xDBFF)

        {

            // preserve the order of the surrogate pair code units

            output[outputIndex + 1] = input[inputIndex];

            output[outputIndex] = input[inputIndex - 1];

            outputIndex++;

            inputIndex--;

        }

        else

        {

            output[outputIndex] = input[inputIndex];

        }

    }

 

    return new string(output);

}

Posted by Bradley Grainger at 9:07 PM | Comments (4) | TrackBack

Detecting Bindings that should be OneTime

In WPF, a Binding's source can be any .NET object; the target of the Binding will be updated when the specified property on that source changes. This works best when the source property is a DependencyProperty, or when the source object implements INotifyPropertyChanged; these objects have built-in support for property value changed notifications. In other cases, the ComponentModel infrastructure (as exposed by the PropertyDescriptor class) stores the source object in a global table in order to track clients who wish to be notified when a property value changes.

Binding to a regular property of a regular .NET object (that doesn't implement INotifyPropertyChanged) has two drawbacks:

  1. It may be needlessly inefficient. If, for example, the source object is not implementing INotifyPropertyChanged because it's immutable, creating and attaching value changed handlers is unnecessary overhead.
  2. It can cause a memory leak.

Both these problems can be eliminated by setting the Mode of the Binding to OneTime, but in a large application, determining all the bindings that could be OneTime is not an easy task. Some spelunking (with .NET Memory Profiler and .NET Reflector) showed that the (internal) ReflectTypeDescriptionProvider class has a static Hashtable containing all objects that have had value changed handlers added. A common reason for objects to end up in that Hashtable is their participation in a WPF binding, so enumerating this Hashtable at runtime can help track down bindings that may need to be changed. (And if an object is never removed from this hashtable, that may be a sign of a memory leak.)

This method uses reflection to dump the contents of the ReflectTypeDescriptionProvider._propertyCache hashtable for diagnostic purposes (the definition of the ReflectPropertyDescriptorInfo class is given later):

private static ReadOnlyCollection<ReflectPropertyDescriptorInfo> GetReflectPropertyDescriptorInfo()

{

    List<ReflectPropertyDescriptorInfo> listInfo = new List<ReflectPropertyDescriptorInfo>();

 

    // get the ReflectTypeDescriptionProvider._propertyCache field

    Type typeRtdp = typeof(PropertyDescriptor).Module.

        GetType("System.ComponentModel.ReflectTypeDescriptionProvider");

    FieldInfo propertyCacheFieldInfo = typeRtdp.GetField("_propertyCache",

        BindingFlags.Static | BindingFlags.NonPublic);

    Hashtable propertyCache = (Hashtable) propertyCacheFieldInfo.GetValue(null);

 

    if (propertyCache != null)

    {

        // try to make a copy of the hashtable as quickly as possible (this object can be accessed by other threads)

        DictionaryEntry[] entries = new DictionaryEntry[propertyCache.Count];

        propertyCache.CopyTo(entries, 0);

 

        FieldInfo valueChangedHandlersFieldInfo = typeof(PropertyDescriptor).GetField("valueChangedHandlers",

            BindingFlags.Instance | BindingFlags.NonPublic);

 

        // count the "value changed" handlers for each type

        foreach (DictionaryEntry entry in entries)

        {

            PropertyDescriptor[] pds = (PropertyDescriptor[]) entry.Value;

            if (pds != null)

            {

                foreach (PropertyDescriptor pd in pds)

                {

                    Hashtable valueChangedHandlers = (Hashtable) valueChangedHandlersFieldInfo.GetValue(pd);

                    if (valueChangedHandlers != null && valueChangedHandlers.Count != 0)

                        listInfo.Add(new ReflectPropertyDescriptorInfo(entry.Key.ToString(), pd.Name,

                            valueChangedHandlers.Count));

                }

            }

        }

    }

 

    listInfo.Sort();

    return listInfo.AsReadOnly();

}

The following code implements a window that displays all the properties that were found. It can be used by adding it to a WPF application and creating a special diagnostic button or keystroke that opens the window. You can open two windows and compare the lists side-by-side, or use the Refresh button to regenerate the list (after interacting with your application's UI) to see if any properties have been added or removed.

ReflectPropertyDescriptorWindow.xaml:

<Window x:Class="OneTimeBinding.ReflectPropertyDescriptorWindow"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:src="clr-namespace:OneTimeBinding"

    Title=".NET Properties used in Binding Paths" Height="450" Width="450" WindowStartupLocation="CenterScreen"

    DataContext="{Binding RelativeSource={RelativeSource Self}}">

 

    <Window.Resources>

        <ResourceDictionary>

            <DataTemplate DataType="{x:Type src:ReflectPropertyDescriptorInfo}">

                <StackPanel Orientation="Horizontal">

                    <TextBlock Text="{Binding TypeName, Mode=OneTime}"/>

                    <TextBlock>.</TextBlock>

                    <TextBlock FontWeight="Bold" Text="{Binding PropertyName, Mode=OneTime}"/>

                    <TextBlock Text="{Binding DisplayHandlerCount, Mode=OneTime}"/>

                </StackPanel>

            </DataTemplate>

        </ResourceDictionary>

    </Window.Resources>

 

    <DockPanel>

        <Button DockPanel.Dock="Top" Margin="4"

            Click="RefreshButton_Click">_Refresh</Button>

        <ScrollViewer Margin="4">

            <ItemsControl ItemsSource="{Binding ReflectProperties}"/>

        </ScrollViewer>

    </DockPanel>

</Window>

 

ReflectPropertyDescriptorWindow.xaml.cs:

public partial class ReflectPropertyDescriptorWindow : Window

{

    public ReflectPropertyDescriptorWindow()

    {

        InitializeComponent();

        ReflectProperties = GetReflectPropertyDescriptorInfo();

    }

 

    public static readonly DependencyProperty ReflectPropertiesProperty =

        DependencyProperty.Register("ReflectProperties", typeof(ReadOnlyCollection<ReflectPropertyDescriptorInfo>),

        typeof(ReflectPropertyDescriptorWindow), new PropertyMetadata());

 

    public ReadOnlyCollection<ReflectPropertyDescriptorInfo> ReflectProperties

    {

        get { return (ReadOnlyCollection<ReflectPropertyDescriptorInfo>) GetValue(ReflectPropertiesProperty); }

        set { SetValue(ReflectPropertiesProperty, value); }

    }

 

    private void RefreshButton_Click(object sender, RoutedEventArgs e)

    {

        ReflectProperties = GetReflectPropertyDescriptorInfo();

    }

 

    private static ReadOnlyCollection<ReflectPropertyDescriptorInfo> GetReflectPropertyDescriptorInfo()

    {

        // as shown above

    }

}

And finally, the definition of the immutable ReflectPropertyDescriptorInfo object, which is used as the source of a OneTime binding in the UI:

public sealed class ReflectPropertyDescriptorInfo : IEquatable<ReflectPropertyDescriptorInfo>,

    IComparable<ReflectPropertyDescriptorInfo>

{

    public ReflectPropertyDescriptorInfo(string typeName, string propertyName, int handlerCount)

    {

        m_typeName = typeName;

        m_propertyName = propertyName;

        m_handlerCount = handlerCount;

    }

 

    public string TypeName

    {

        get { return m_typeName; }

    }

 

    public string PropertyName

    {

        get { return m_propertyName; }

    }

 

    public int HandlerCount

    {

        get { return m_handlerCount; }

    }

 

    public string DisplayHandlerCount

    {

        get { return m_handlerCount == 1 ? "" : string.Format(CultureInfo.InvariantCulture,

            " ({0:n0} handlers)", m_handlerCount); }

    }

 

    public int CompareTo(ReflectPropertyDescriptorInfo other)

    {

        if (object.ReferenceEquals(other, null))

            return 1;

 

        int compareResult = m_typeName.CompareTo(other.m_typeName);

        if (compareResult == 0)

            compareResult = m_propertyName.CompareTo(other.m_propertyName);

        if (compareResult == 0)

            compareResult = m_handlerCount.CompareTo(other.m_handlerCount);

        return compareResult;

    }

 

    // Implementations of Equals, GetHashCode, operators, etc. elided for brevity

 

    readonly string m_typeName;

    readonly string m_propertyName;

    readonly int m_handlerCount;

}

Posted by Bradley Grainger at 6:57 PM | Comments (1) | TrackBack

October 16, 2008

Going to PDC

I'm going to PDC on 26 October. If you'd like to meet up there, send a note with your contact details to bgrainger at logos.com.

Posted by Bradley Grainger at 6:33 PM | Comments (0) | TrackBack

October 15, 2008

Keep your WPF UI responsive

Hopefully, everyone writing Windows applications understands the value of keeping the UI responsive. That is, we all know that it is important to avoid long-running or long-waiting work in the UI thread, because it prevents the window from doing anything - it can't respond to keystrokes or clicks; it can't even update its display. So, we make sure that any work we do is so fast that the user won't notice, or we move that work into background threads.

Still, it is easy to miss something, especially when an operation usually completes quickly, but can be much slower under certain circumstances. Until recently, we could get away with it from time to time - in fact, we might display a "Working" message right before we started the work, or we might set the mouse cursor to the "hourglass" on the hopes that the user won't get too impatient and terminate the application.

These days, however, Windows is much less forgiving. Under Windows Vista, at least, if a window is not responsive for five seconds or so, the window flickers a bit and Windows adds the humiliating "(Not Responding)" message to the window caption.

WinFormsAppNotResponding

Even so, surely the user could deal with that from time to time. A temporarily frozen user interface isn't the end of the world; after all, that program is working hard! Just let it finish and everything will be fine again.

Unfortunately, things get much worse when running a WPF application under Windows Vista with Aero enabled (the glassy window captions). If that application becomes unresponsive, you get the "(Not Responding)" message, as well as something we like to call the "black screen of death" - the entire content of the window goes pitch black.

WpfAppNotResponding

This is surely unacceptable - nothing says "terminate me" like a black window. In the end, I suppose this will be best for the user, because we're trying extra hard to make sure that nothing we do on the UI could ever take five seconds. But it's certainly a lot harder than displaying the trusty old hourglass.

Posted by Ed Ball at 9:11 AM | Comments (0) | TrackBack

October 7, 2008

Passing An Array Parameter To SQL Server Stored Procedures

SQL Server (2000 & 2005) does not support array parameters for stored procedures. As a workaround, an array of values can be passed into SQL Server as a delimited string.

There are many articles on the web on how to do this, but this is my preferred method because the conversion of the delimited string into table values is done in a reusable function and the function itself can be placed inside a select query and act as a table.

First, we need to convert a delimited string to a table of values. This can be done through the following table-valued function:


CREATE Function [dbo].[fnSplit](@text text, @delimitor nchar(1))

 

RETURNS

@table TABLE

(

    [Index] int Identity(0,1),

    [SplitText] varchar(10)

)

AS

 

BEGIN

    declare @current varchar(10)

    declare @endIndex int

    declare @textlength int

    declare @startIndex int

 

    set @startIndex = 1

 

    if(@text is not null)

    begin

        set @textLength = datalength(@text)

 

        while(1=1)

        begin

            set @endIndex = charindex(@delimitor, @text, @startIndex)

 

            if(@endIndex != 0)

            begin

                set @current = substring(@text,@startIndex, @endIndex - @StartIndex)

                Insert Into @table ([SplitText]) values(@current)

                set @startIndex = @endIndex + 1   

            end

            else

            begin

                set @current = substring(@text, @startIndex, datalength(@text)-@startIndex+1)

                Insert Into @table ([SplitText]) values(@current)

                break

            end

        end

 

    end

 

    return

END


To use this function, simply treat it as a table in the query:

select SplitText

from dbo.fnSplit('a,b,c',',')


Returns:

a
b
c


The complete process goes like this:

1. Convert the array of values to a delimited string.
2. Pass this string to the stored procedure.
3. Use the above fnSplit function to convert the string to a table of values which can be used in queries.

Posted by Bill Simpkins at 4:10 PM | Comments (4) | TrackBack

Introduction: Bill Simpkins

In high school I was horrible at anything remotely technical, except for music. I began teaching guitar and music theory when I was 16 and then took my own sweet time in community college while I was singing and playing guitar in various rock bands. In the mid 90's I took an interest in astronomy, which led to physics, which led to math and programming. In 2001 I received my B.S in Mathematics from WWU(with about all the coursework for a physics). I occasionally go back and take classes.

I continued to work as an audio engineer, which I did all through college to pay the bills, until I became a financial analyst at a large insurance company(I got married). I mined tons of data and became rather good with Oracle and SQL Server, along with creating makeshift dashboards using Java, XML, Javascript, HTML and butchered PostScript. I later worked at a hospital doing data analysis and creating data mining tools in C# and VB. On my free time I created simulators in C++ to test gambling schemes.

I started work at LOGOS as a Web Developer in October 2007 and I enjoy the innovative environment very much. I work on a variety of projects including, but not limited to, data integration and CRM development.

I am currently interested in Object Databases, physics processing and mathematics engines. When I'm not programming I climb cliffs and mountains, snowboard and play music.

Posted by Bill Simpkins at 2:12 PM | Comments (0) | TrackBack

October 6, 2008

Using "Background Processing Mode" from C#

The Windows Vista kernel added support for I/O and memory priorities. These allow background work (such as search indexing or virus scanning) to reduce its impact on foreground applications beyond what is possible simply by using a low thread CPU priority. According to the SetThreadPriority documentation, "For threads that perform background work such as file I/O, network I/O, or data processing, it is not sufficient to adjust the CPU scheduling priority; even an idle CPU priority thread can easily interfere with system responsiveness when it uses the disk and memory."

Applications can opt in to low I/O and memory priority by passing new flags to SetThreadPriority(THREAD_MODE_BACKGROUND_BEGIN and THREAD_MODE_BACKGROUND_END) or SetPriorityClass (PROCESS_MODE_BACKGROUND_BEGIN and PROCESS_MODE_BACKGROUND_END).

These new priority levels aren't exposed through the .NET Framework, but can be accessed by using P/Invoke. First, declare the constants and functions from the Windows API:

internal static class Win32

{

    public const int THREAD_MODE_BACKGROUND_BEGIN = 0x00010000;

    public const int THREAD_MODE_BACKGROUND_END = 0x00020000;

}

 

internal static class NativeMethods

{

    [DllImport("Kernel32.dll", ExactSpelling = true)]

    public static extern IntPtr GetCurrentThread();

 

    [DllImport("Kernel32.dll", ExactSpelling = true)]

    [return: MarshalAs(UnmanagedType.Bool)]

    public static extern bool SetThreadPriority(IntPtr hThread, int nPriority);

}

Second, write a C# wrapper for those functions. I use Thread.BeginThreadAffinity to notify the runtime (strictly speaking, the CLR host) that the code that's being executed depends on the identity of the underlying OS thread. The return type, Scope, has been covered already on this blog.

public static class ThreadUtility

{

    /// <summary>

    /// Puts the current thread into background processing mode.

    /// </summary>

    /// <returns>A Scope that must be disposed to leave background processing mode.</returns>

    [SecurityPermission(SecurityAction.Demand, Flags=SecurityPermissionFlag.ControlThread)]

    public static Scope EnterBackgroundProcessingMode()

    {

        Thread.BeginThreadAffinity();

        IntPtr hThread = SafeNativeMethods.GetCurrentThread();

        if (IsWindowsVista() && NativeMethods.SetThreadPriority(hThread,

            Win32.THREAD_MODE_BACKGROUND_BEGIN))

        {

            // OS supports background processing; return Scope that exits this mode

            return Scope.Create(() =>

            {

                NativeMethods.SetThreadPriority(hThread, Win32.THREAD_MODE_BACKGROUND_END);

                Thread.EndThreadAffinity();

            });

        }

 

        // OS doesn't support background processing mode (or setting it failed)

        Thread.EndThreadAffinity();

        return Scope.Empty;

    }

 

    // Returns true if the current OS is Windows Vista (or Server 2008) or higher.

    private static bool IsWindowsVista()

    {

        OperatingSystem os = Environment.OSVersion;

        return os.Platform == PlatformID.Win32NT && os.Version >= new Version(6, 0);

    }

}

Third, use the wrapper like so:

using (ThreadUtility.EnterBackgroundProcessingMode())

{

    PerformSomeBackgroundWork();

}

Note that this only has effect on Windows Vista, Windows Server 2008, and later; you'd also want to lower Thread.Priority during the background work if your application runs on earlier operating systems.

And while this does appear to work quite nicely in testing, it could actually be dangerous in production code. It's possible that this could have unpredictable and hazardous interactions with the garbage collector, the finalizer thread, or other components of the .NET Runtime. Furthermore, if a CLR host ever multiplexes many managed threads to one OS thread, changing the priority of the OS thread would be too heavy-handed. Perhaps a future version of the framework will expose background processing mode to managed threads in a safe way; until then it's probably best to consider advanced native threading features (background mode, fibers, CPU affinity, etc.) to be off limits to managed code.

Posted by Bradley Grainger at 8:17 AM | Comments (0) | TrackBack

October 1, 2008

Displaying a Splash Screen with C++ (Part IV)

This is Part IV of a series on creating a splash screen application in native code. For more information, see the Introduction and the Code License.

Part IV: Dismissing the Splash Screen

When the WPF application has initialised and is ready to display its main window, the splash screen needs to be dismissed. The easiest way to accomplish this is by having a named event that the native application creates and the WPF application sets.

Firstly, we'll create the named event that will dismiss the splash screen. This also has the beneficial side effect that we can prevent more than one splash screen application from running at once. (Note that the WPF application will also need to ensure that only one instance runs at a time, if that is desired. On the other hand, if you want to be able to launch multiple instances of the splash screen and WPF applications at once, each will need to have a unique event.)

// create the named close splash screen event, making sure we're the first process to create it

SetLastError(ERROR_SUCCESS);

HANDLE hCloseSplashEvent = CreateEvent(NULL, TRUE, FALSE, _T("CloseSplashScreenEvent"));

if (GetLastError() == ERROR_ALREADY_EXISTS)

    ExitProcess(0);

Once we know it's safe to continue running, we can use the code shown in Parts I-III to display the splash screen and launch the WPF application, then wait for the "close splash screen" event to be set or the WPF application to exit; once either of these events happens, it's time to dismiss the splash screen.

// call LoadSplashImage() etc.

// call SetSplashImage() etc.

 

// launch the WPF application

HANDLE hProcess = LaunchWpfApplication();

AllowSetForegroundWindow(GetProcessId(hProcess));

 

// display the splash screen for as long as it's needed

HANDLE aHandles[2] = { hProcess, hCloseSplashEvent };

PumpMsgWaitForMultipleObjects(2, &aHandles[0], INFINITE);

The PumpMsgWaitForMultipleObjects method has not yet been defined. It's similar (in API) to the Win32 WaitForMultipleObjects function, but it also dispatches window messages as they arrive. Since we have created a window on this thread, running a message pump is essential. We use MsgWaitForMultipleObjects to wait for either of two HANDLEs while also being woken up when a window message arrives. (Note that this implementation is more generic than is necessary in this example: the timeout is always INFINITE, and there's no outer message loop that would need to reprocess the WM_QUIT message.)

inline DWORD PumpMsgWaitForMultipleObjects(DWORD nCount, LPHANDLE pHandles, DWORD dwMilliseconds)

{

    // useful variables

    const DWORD dwStartTickCount = ::GetTickCount();

 

    // loop until done

    for (;;)

    {

        // calculate timeout

        const DWORD dwElapsed = GetTickCount() - dwStartTickCount;

        const DWORD dwTimeout = dwMilliseconds == INFINITE ? INFINITE :

            dwElapsed < dwMilliseconds ? dwMilliseconds - dwElapsed : 0;

 

        // wait for a handle to be signaled or a message

        const DWORD dwWaitResult = MsgWaitForMultipleObjects(nCount, pHandles, FALSE, dwTimeout, QS_ALLINPUT);

        if (dwWaitResult == WAIT_OBJECT_0 + nCount)

        {

            // pump messages

            MSG msg;

            while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) != FALSE)

            {

                // check for WM_QUIT

                if (msg.message == WM_QUIT)

                {

                    // repost quit message and return

                    PostQuitMessage((int) msg.wParam);

                    return WAIT_OBJECT_0 + nCount;

                }

 

                // dispatch thread message

                TranslateMessage(&msg);

                DispatchMessage(&msg);

            }

        }

        else

        {

            // timeout on actual wait or any other object

            return dwWaitResult;

        }

    }

}

Lastly, the WPF application needs to signal the splash screen application to close. (Finally, some C# code!)

private void CloseSplashScreen()

{

    // signal the native process (that launched us) to close the splash screen

    using (var closeSplashEvent = new EventWaitHandle(false,

        EventResetMode.ManualReset, "CloseSplashScreenEvent"))

    {

        closeSplashEvent.Set();

    }

}

As long as the same name is used, the event will be shared across the two processes and the splash screen application will exit when the event is set.

On my slow XP computer, the native application displays the splash screen in well under a second, even from a cold start. Sometimes it's necessary to drop back to native code for small feature areas with strict performance demands. Displaying UI as soon as possible when the application is launched is one of those areas; a few hundred lines of native code can result in a perceived decrease in application startup time and a better experience for the end user.

Posted by Bradley Grainger at 8:31 AM | Comments (5) | TrackBack