Posts
72
Comments
234
Trackbacks
162
December 2011 Entries
Wrong SGen For x64 Targets Used

This is another x64 issue where I was really surprised that it does exist. When you compile in VS2010/MsBuild a managed target for which the corresponding serialization assembly is generated you will find that all works until you try to compile for 64 bit. There you will get:

"SGEN : error : An attempt was made to load an assembly with an incorrect format: xxxx.dll."

Here is the 32 bit SGen is called for the 64 bit target which cannot load a 64 bit assembly. I have no idea why MS did not provide a SGen compiled as Any CPU but the Windows SDK does bring in a 32 and 64 bit version of SGen. After a little digging around I quickly found that I can manually set the SGenToolPath MsBuild property. I could have patched

C:\Windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets

directly but then I would have changed the checksum of the file and future .NET Framework patches would not be able to patch this file anymore. Since the VS 2010 project format builds completely upon MsBuild I can set the path also in my Studio project for each build mode correctly.

By the way it is not enough to simply set “Generate serialization assembly” to On to get an xxx.XmlSerializers assembly for all public types.

image

The SGen task of the .NET Framework does by default only create an serialization assembly if it does contain types derived from some web proxy classes used for Web Services. To create an xml serializer type for all public types of my assembly I needed therefore to set for all build modes

    <GenerateSerializationAssemblies>On</GenerateSerializationAssemblies>
    <SGenUseProxyTypes>false</SGenUseProxyTypes>

MsBuild does automatically add all direct assembly references to SGen as well to allow SGen to create the serialization code for types which have base classes in other assemblies. This service is good but it is not perfect. If you have a deeper inheritance tree you will need more than all direct references of your assembly. Since SGen is located in the Windows SDK there is no way to specify a probing path relative to my build output directory since .NET allows only directories below the executable location as additional probing paths. I decided to make life easy and simply copy SGen to my build output directory to cover all possible cases.

At first I do need the path to the right sgen.exe in the current build mode. For this I did define the property __SdkSgenTool. I do need two tries to resolve the right SGen since the SDK can be in Program Files (32 bit Windows) or Program Files (x86) (64 bit Windows). Then I do copy sgen.exe to the build output directory $(TargetDir).

The only missing thing is that I do need to set SGenToolPath to my build output directory. This was harder as expected since as a normal property it was overwritten by other MsBuild tasks. The solution that finally did work was to create the already existing property and set the value to its final value when no other tasks could interfere.

Below is the “code” to make Sgen work in 64 bit. You need to define the __SdkSgenTool variable in all build modes since the post build steps like copy are executed regardless of the build mode.

  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
   ….
    <GenerateSerializationAssemblies>On</GenerateSerializationAssemblies>
    <SGenUseProxyTypes>false</SGenUseProxyTypes>
    <__SdkSgenTool Condition="exists('C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\x64\sgen.exe')">C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\x64\sgen.exe</__SdkSgenTool>
    <__SdkSgenTool Condition="exists('C:\Program Files\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\x64\sgen.exe')">C:\Program Files\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\x64\sgen.exe</__SdkSgenTool>
  </PropertyGroup>

  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <Target Name="BeforeBuild">
    <Copy SourceFiles="$(__SdkSgenTool)" DestinationFiles="$(TargetDir)\sgen.exe" SkipUnchangedFiles="true" />
    <CreateProperty Value="$(TargetDir)">
      <Output TaskParameter="Value" PropertyName="SGenToolPath" />
    </CreateProperty>

 

I have heard that this issue will be fixed with VS2012 which is a good thing. MsBuild is very powerful but I do prefer a working build in all build modes without any surprises. The path to SGen is hard coded since I do not expect that somebody does install Visual Studio to another location as Program Files. The SDK directories below Microsoft SDKs\Windows\ are normal downloadable Windows SDKs if they contain only a version number. If there is an A at the end these SDKs are the ones which Visual Studio does bring with.

Posted On Saturday, December 10, 2011 7:43 PM | Feedback (0)
Microsoft == x86 && Sometimes !x64

Whenever I do use Visual Studio and try to compile something under 64 bit I run into problems. It seems that most MS Devs for Visual Studio and the relevant tool chain are still mainly writing 32 bit applications.

Here are some of the latest issues I did run into.

 

COM applications targeting x64 are still using x32 as target platform for the MIDL compiler by default

Resolution: You have to select in the UI MIDL – Target Environment X64 by yourself. Alternatively you can edit the vcxproj file directly and set the flag there:

  <Midl>
  <TargetEnvironment>X64</TargetEnvironment>

 

The othere issue was that on my dev machine Visual Studio 2010 SP1 did freeze quite often. The dump showed that it did hang while loading  the assembly Microsoft.VSDesigner. I have filed a Connect issue for this but I got not any helpful feedback yet. In the meantime I did find out by myself why VS was hanging. Once the hang did occur it was freezing quite often which is good for a repro but very annoying if there is nothing you can do about it. I did send my error reports to MS every time hoping that they resolve the issue before VS2012 is released. The Connect support person requested another dump from me this time taken with VS directly. I have to remember that for my own trouble shooting that VS can now also take dumps which really helps. The interesting thing was that the VS call stack was more helpful since it seemed to resolve the symbols better. The deadlock has frozen the UI thread while some stack frames from MSI were on it.

 

    ntdll.dll!_ZwWaitForSingleObject@12() + 0x15 bytes   
    ntdll.dll!_ZwWaitForSingleObject@12() + 0x15 bytes   
    kernel32.dll!_WaitForSingleObjectExImplementation@12() + 0x43 bytes   
    msenv.dll!_VsCoCreateAggregatedManagedObject() + 0xe2 bytes   
    msenv.dll!_VsLoaderCoCreateInstanceUnknown() + 0x8e bytes   
    msenv.dll!CVsLocalRegistry4::CreateInstance() + 0x4a bytes   
    msenv.dll!CXMLMemberIndexService::GetCulture() + 0x17f5 bytes   
    msenv.dll!CXMLMemberIndexService::LocateAndOpenXMLFile() + 0x14 bytes   
    msenv.dll!CXMLMemberIndexService::CreateXMLMemberIndex() + 0x78 bytes   
    cslangsvc.dll!CMetaDataLoader::EnqueueMemberIndexRequest() + 0xe80cc bytes   
    cslangsvc.dll!CMetaDataTypeData::GetDocumentationComment() + 0x1b bytes   
    cslangsvc.dll!CSymbolDescription::CPartCollector::ConditionallyAddDocCommentParts<CTypeData>() + 0x32 bytes   
    cslangsvc.dll!CSymbolDescription::CTypeProviderHelper::TryExecute() + 0x278 bytes   
    cslangsvc.dll!CSymbolDescription::CNameProviderVisitor::Visit() + 0x56 bytes   
    cslangsvc.dll!CTypeProvider::Accept() + 0x13 bytes   
    cslangsvc.dll!CSymbolDescription::CNameProviderVisitor::TryExecute() + 0x2a bytes   
    cslangsvc.dll!CSymbolDescription::TryGetDescription() + 0x37 bytes   
    cslangsvc.dll!CSymbolDescription::TryAppendDescription() + 0x61 bytes   
    cslangsvc.dll!CSpanBinder::TryExecuteAndGetDescription() + 0x88 bytes   
    cslangsvc.dll!CQuickInfo::TryGetRawIntelliSenseQuickInfo() + 0x81 bytes   
    cslangsvc.dll!CQuickInfo::TryGetFullIntelliSenseQuickInfo() + 0x4d bytes   
    cslangsvc.dll!CQuickInfo::TryExecute() + 0x3e bytes   
    cslangsvc.dll!CEditFilter::GetDataTipText() + 0xdd bytes   
    cslangsvc.dll!CVsEditFilter::GetDataTipText() + 0x52 bytes   
    user32.dll!_InternalCallWinProc@20() + 0x23 bytes   
    user32.dll!_UserCallWinProcCheckWow@32() + 0xb7 bytes   
    user32.dll!_DispatchMessageWorker@8() + 0xed bytes   
    user32.dll!_DispatchMessageW@4() + 0xf bytes   
    msi.dll!MsiUIMessageContext::RunInstall() + 0x21231 bytes   
    msi.dll!RunEngine() + 0xb3 bytes   
    msi.dll!ConfigureOrReinstallFeatureOrProduct() + 0xfa bytes   
    msi.dll!_MsiReinstallFeatureW@12() + 0x66 bytes   
    msi.dll!ProvideComponent() + 0x10957 bytes   
    msi.dll!ProvideComponentFromDescriptor() + 0x154 bytes   
    msi.dll!_MsiProvideAssemblyW@24() + 0x437 bytes   
    msenv.dll!_VsCoCreateAggregatedManagedObject() + 0xe2 bytes
   
    msenv.dll!_VsLoaderCoCreateInstanceUnknown() + 0x8e bytes   
    msenv.dll!CVsLocalRegistry4::CreateInstance() + 0x4a bytes   
    msenv.dll!CXMLMemberIndexService::GetCulture() + 0x17f5 bytes   
    msenv.dll!CXMLMemberIndexService::LocateAndOpenXMLFile() + 0x14 bytes   
    msenv.dll!CXMLMemberIndexService::CreateXMLMemberIndex() + 0x78 bytes   
    cslangsvc.dll!CMetaDataLoader::EnqueueMemberIndexRequest() + 0xe80cc bytes   
    cslangsvc.dll!CMDMemberData::GetDocumentationComment() + 0x51 bytes   
    cslangsvc.dll!CSymbolDescription::CPartCollector::ConditionallyAddDocCommentParts<CMemberData>() + 0x2f bytes   
    cslangsvc.dll!CSymbolDescription::CMemberProviderHelper::TryExecute() + 0xdc bytes   
    cslangsvc.dll!CSymbolDescription::CNameProviderVisitor::Visit() + 0x53 bytes   
    cslangsvc.dll!CAbstractNameProviderBoolDefaultVisitor::Visit() + 0x2b bytes   
    cslangsvc.dll!CAggregateMemberProvider::Accept() + 0x16 bytes   
    cslangsvc.dll!CSymbolDescription::CNameProviderVisitor::TryExecute() + 0x2a bytes   
    cslangsvc.dll!CSymbolDescription::TryGetDescription() + 0x37 bytes   
    cslangsvc.dll!CSymbolDescription::TryAppendDescription() + 0x61 bytes   
    cslangsvc.dll!CSpanBinder::TryExecuteAndGetDescription() + 0x88 bytes   
    cslangsvc.dll!CQuickInfo::TryGetRawIntelliSenseQuickInfo() + 0x81 bytes   
    cslangsvc.dll!CQuickInfo::TryGetFullIntelliSenseQuickInfo() + 0x4d bytes   
    cslangsvc.dll!CQuickInfo::TryExecute() + 0x3e bytes   
    cslangsvc.dll!CEditFilter::GetDataTipText() + 0xdd bytes   
    cslangsvc.dll!CVsEditFilter::GetDataTipText() + 0x52 bytes   

The language service tries to get some tool tip (GetDataTipText) text and tries to load an assembly. For reasons unknown to me MSI is used to get an assembly and install it if it is not already present. Since the requested assembly was not found an installation was performed. While the installation is running MSI does pump again window messages which does let VS again to call GetDataTipText since my mouse was still hovering over some code element in the editor. MSI would be called again to resolve the same assembly but since the installation is already running VS does block. There is another helper thread running at cslangsvc.dll!CBackgroundQueue::ExecuteRequests() which seems wait for the msi installation to finish on the UI thread but since MSI cannot report any progress or failure back via the Window message loop we have a classic hang. You could ask how I do know about the other thread holding the same lock?

Starting with Windows Vista WCT (Wait Chain Traversal) was added to the Kernel which is accessible via some Windbg extensions or much easier via the Windows Resource Monitor (at the comamnd line enter: perfmon.exe /res). Hung processes are marked as red and are displayed at first in the process list (very nice). From there it is a simple right click on the hung process to analyze the locks. There you can see directly which thread is waiting for which other thread/s. No more kernel debugging!

The question that remains is why the MSI installation did never complete anyway which would have caused only one VS hang. To see what is going on on the MSI side you need to enable MSI logging. With a little practice you can quickly find the interesting lines:

=== Verbose logging started: 05.12.2011 15:57:51 Build type: SHIP UNICODE 5.00.7600.00 Calling process: C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\devenv.exe ===
Command Line: REINSTALL=SysClrTypFeature REINSTALLMODE=pocmus CURRENTDIRECTORY=C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE CLIENTUILEVEL=3 CLIENTPROCESSID=11280
MSI (s) (5C:38) [06:29:54:573]: SOURCEMGMT: Failed to resolve source
MSI (s) (5C:38) [06:29:54:573]: Product: Microsoft SQL Server System CLR Types (x64) -- Error 1706. An installation package for the product Microsoft SQL Server System CLR Types (x64) cannot be found. Try the installation again using a valid copy of the installation package 'SQLSysClrTypes_amd64_enu.msi'.

The MSI which was tried to patch was Microsoft SQL Server System CLR Types (x64). For some reason the original MSI was no longer cached in the \Windows\Installer directory which caused the installation silently to fail. From there it is easy to resolve the issue: Install the original Microsoft SQL Server System CLR Types (x64) MSI and you should see no more failed patching while VS is running.

This issue was really annoying but since then VS 2010 is running happily on my machine without any hangs. I am really happy that I was able to resolve this problem so easily. The only issue I still do have that VS2010 is a memory hog and I do pity the poor people with slow hard discs. On these machines VS 2010 is no fun to use.

Posted On Friday, December 09, 2011 10:53 AM | Feedback (0)
Simple Producer Consumer With Tasks And .NET 4

Threading was never so easy since .NET 4 with the TPL has been released. I know I am a bit late but there are so many nice things which might still be new to many of us. The IEnumerable interface has become famous with the introduction of LINQ but many of us have not yet realized that IEnumerable<T> and  T[] or List<T> can be exchanged in many cases but there are cases where it is important to fall back to a pure IEnumerable<T> if you want to support lazy evaluation. .NET 4 has for example taken advantage of the lazy nature of IEnumerable<T> with the introduction of Directory.EnumerateFiles which returns immediately until the first file is found. Previously you had only the option to call Directory.GetFiles which does potentially search for a long time and will only return when all matching files have been found. This can make a big difference if you search recursively in a big file tree or a directory with many files. I had up to 40s delays in some applications which did process a large directory. 40s waiting time until you can process the first file is certainly not something you want. I did solve this issue in .NET 3.5 with DirectorySearcherAsync which did work quite well.

One additional optimization you do is to continue searching for your files on another thread when the first file has been found and returned by the enumerator. My DirectorySearcherAsync does this as well. This is working but the code is quite complex. We can get the same functionality much easier with .NET 4. The pattern applied here is a simple producer consumer pattern where the producer and the consumer live on different threads. It is easy to transform an IEnumerable<T> to an IEnumerable<T> which does the enumeration on another thread and gives you access to the returned items via a blocking queue in a thread safe manner. The new concurrent collections in .NET 4 are named a little bit different and we can find it under the name BlockingCollection<T> which was made for this exact purpose. Since I did not want to return a BlockingCollection<T> directly but I wanto to give you the possibility to

  • Block with a timeout to get all elements fetched so far
  • Block until the next item was found
  • Cancel a running operation

I have created an extension method not with the signature

static BlockingCollection<T> EnumerateAsync(this IEnumerable<T> source)

but these two

public static IEnumerable<T> EnumerateAsync<T>(this IEnumerable<T> source)
public static IEnumerable<T[]> EnumerateAsync<T>(this IEnumerable<T> source, CancellationToken token, int millisecondsTimeout=-1, int maxCount=0)

The first one is simply a convenience method over the second one which does basically cover the use case you expect from a responsive UI. I do need the ability to cancel the current operation which can be done nicely with a CancellationToken if the user has changed his mind and pressed impatiently the stop button. At the same time he does want to see the results as fast as possible when they are ready. This is done with the millisecondsTimeout flag which does unblock the enumerator e.g. every 500ms to give you the e.g. found files so far. If none have been found yet you are not woken up since there is no work to do. At the same it is also disturbing to update the UI with 10 000 entries at once even if it did take only 500ms. There is need to update the UI more often than every 500ms if many items are pending. For this case the maxCount flag is there to give you at most e.g. 100 items at once. For your exact use case the numbers may turn out different but for file based operations they turned out to be quite good. You did notice that the return value of the second method is not IEnumerable<T> but IEnumerable<T[]>?

The reason is simple: Chunking. What would happen if you get 10000 items in 500ms? You have most likely some code like this in the beginning.

void WorkerThread()
{
    foreach(var x in Directory.EnumerateFiles(“C:\\”,”*.*”, SearchOption.AllDirectories)
                              .EnumerateAsync(source, token,500, 100)) 
         UpdateUI(x);
}

You will find that 10K updates in 500ms will render your UI unusable and you do need a way to update your UI less often with a chunk of gathered data. Every UpdateUI call will need to post a message into the message loop of your to reach the UI thread which is a costly undertaking. If x is not of type T but of type T[] you get a coarse grained UI update. In the previous example we did reduce the pressure on the window message pump loop by a factor 100 which feels much better.

using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using System.Threading;

namespace AsyncEnumeration
{
    public static class Extensions
    {
        /// <summary>
        /// Start enumerating on another thread. The returned enumerator blocks until the next element
        /// is available or the source enumeration has no more elements.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source">Source enumerable</param>
        /// <returns>Async enumerable</returns>
        public static IEnumerable<T> EnumerateAsync<T>(this IEnumerable<T> source)
        {
            foreach (var t in EnumerateAsync(source, CancellationToken.None,-1,1))
            {
                yield return t[0];
            }
        }

        /// <summary>
        /// Start enumerating on another thread. The returned enumerator blocks until the next element
        /// is available or the source enumeration has no more elements.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source">source enumerable</param>
        /// <param name="token">Cancellation token if cancellation support is desired or CancellationToken.None</param>
        /// <param name="millisecondsTimeout">Maximum number of milliseconds to wait until the so far fetched elements are returned</param>
        /// <param name="maxCount">Maximum number of elements to fetch before they are returned</param>
        /// <returns>An enumerable with an array of fetched elements to support chunking in asynchronous operations.</returns>
        public static IEnumerable<T[]> EnumerateAsync<T>(this IEnumerable<T> source, CancellationToken token, int millisecondsTimeout=-1, int maxCount=0)
        {
            BlockingCollection<T> coll = new BlockingCollection<T>();

            Task t = Task.Factory.StartNew(() =>
                {
                    try
                    {
                        // enumerator on other thread and add element to thread safe collection
                        foreach (var item in source)
                        {
                            token.ThrowIfCancellationRequested();
                            coll.Add(item);
                        }
                    }
                    finally
                    { 
                        // signal collection that no more elements will be added.
                        coll.CompleteAdding();
                    }
                },
                token);

            DateTime last = DateTime.Now;
            TimeSpan timeout = millisecondsTimeout == -1 ? TimeSpan.MaxValue : new TimeSpan(0, 0, 0, 0, millisecondsTimeout);

            List<T> chunk = new List<T>();
            T got = default(T);

            while (!coll.IsCompleted)
            {
                // Get next element or block until timeout
                if (coll.TryTake(out got, millisecondsTimeout, token))
                {
                    chunk.Add(got);
                }

                // if timeout has elapsed or the maximum chunk size has been 
                // reached yield fetched elements
                if( (chunk.Count > 0 && DateTime.Now - last > timeout ) ||
                    (maxCount > 0 && chunk.Count > maxCount))
                {
                    yield return chunk.ToArray();
                    chunk.Clear();
                    last = DateTime.Now;
                }
            }

            // if there are pending elements yield them as well
            if (chunk.Count > 0)
            {
                yield return chunk.ToArray();
            }
            
            // not really necessary but it is a good idea to wait here 
            // to allow exceptions to be thrown by the task here if there were any.
            t.Wait();
        }

        /// <summary>
        /// Wrap an enumerable to cache any yielded elements so far for faster retrieval.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source">Source enumerable</param>
        /// <returns>Caching enumerable</returns>
        public static IEnumerable<T> Cache<T>(this IEnumerable<T> source)
        {
            return new MemoizingEnumerator<T>(source);
        }
    }
}

 

That are some quite simple but powerful methods. I have not yet talked about the last method which does cache the returned elements of the source enumerator. It is sometimes useful to check if there are elements at all before you start doing something. To stay with the directory example I want to validate that the directory query does match any files at all. I can do this by

        IEnumerable<string[]> CheckQuery()
        {
            var cachedQuery = Directory.EnumerateFiles(@"C:\", "*.*", SearchOption.AllDirectories)
                                       .EnumerateAsync(Cancel.Token, 500, 100)
                                       .Cache();

            if (cachedQuery.FirstOrDefault() == null)
            {
                throw new ArgumentException("No files found");
            }

            return cachedQuery;
        }

I do start the async operation and check if there are any matches. If there are none I do throw an exception and if yes I do not want to start over again with the file search but continue. This something you would do during parameter validation in a console application to verify that the entered file queries do yield at least one file.

The MemoizingIterator behind the Cache method is very simple

using System.Collections;
using System.Collections.Generic;

namespace AsyncEnumeration
{
    class MemoizingEnumerator<T> : IEnumerable<T>, IEnumerable
    {
        public MemoizingEnumerator(IEnumerable<T> input)
        {
            _Input = input;
        }

        #region IEnumerable Members

        #endregion

        #region IEnumerable<T> Members

        public IEnumerator<T> GetEnumerator()
        {
            if (_Cache == null)
            {
                _enumerator = _Input.GetEnumerator();
                _Cache = new List<T>();
            }
            else
            { 
                foreach (var cachedValue in _Cache)
                {
                    yield return cachedValue;
                }
            }

            if (_enumerator != null)
            {
                while (_enumerator.MoveNext())
                {
                    _Cache.Add(_enumerator.Current);
                    yield return _enumerator.Current;
                }
                _enumerator = null;
            }
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        IEnumerable<T> _Input;
        IEnumerator<T> _enumerator;
        List<T> _Cache;
    }
}

Now we can create a simple UI but responsive UI with only a few lines of Code to display the file names of our hard drive.

SimpleUI

The code to make it is not much but you have to be careful where you need locking and where not. When you press the Start/Stop button we toggle the text between Start and Stop. If the operation has finished on the worker thread we do switch back to start. But in the code below you will not find any locks. Why? Since we do know that our UI does run (in fact can only run) on one thread with the UI message pump there is no need to protect our self from other threads since the UI is always single threaded.

If you want to dive deeper into threading with .NET I can recommend

If you want to know more about the changes in the TPL with the upcoming .NET 4.5 I can recommend TPL Performance Improvements in .NET 4.5 which does contain a detailed analysis what was changed to make continuations as fast as possible. Continuations are supported with .NET 4.5 via the await keyword which would make the code below even simpler.

using System;
using System.Linq;
using System.Windows.Forms;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace AsyncEnumeration
{
    public partial class AsyncEnumeration : Form
    {
        CancellationTokenSource Cancel = new CancellationTokenSource();
        Task RunningTask = null;

        public AsyncEnumeration()
        {
            InitializeComponent();
            TaskScheduler.UnobservedTaskException += new EventHandler<UnobservedTaskExceptionEventArgs>(TaskScheduler_UnobservedTaskException);
        }

        private void StartStopButton_Click(object sender, EventArgs e)
        {
            if (!TryCancelCurrentOperation())
            {
                StartSearch();
            }
        }

        private void StartSearch()
        {
            StartStopButton.Text = "Stop"; // switch start button to stop button
            ListViewDisplay.Clear();     // delete old content
            string dirName = cDirectory.Text; // get from UI start directory

            // Do the file search not on the UI thread
            RunningTask = Task.Factory.StartNew(() =>
            {
                // EnumerateAsync starts another task which lets the enumeration running while you
                // can fetch data from the chunked results.
                foreach (var dirArray in Directory.EnumerateFiles(dirName, "*.*", SearchOption.AllDirectories)
                                                  .EnumerateAsync(Cancel.Token, 500, 100))
                {
                    // EnumerateAsync returns either when 500ms have elapsed and some data was found
                    // OR 100 items were found. 
                    var listViewItems = (from x in dirArray
                                         select new ListViewItem(x)).ToArray();

                    // block the UI thread as short as possible by adding a bunch of results
                    this.Invoke(new MethodInvoker(() => ListViewDisplay.Items.AddRange(listViewItems)));
                }
            },
            Cancel.Token);

            // When the enumeration task has finished (with or without error)
            // display the Start button again
            var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
            RunningTask.ContinueWith((t) =>
            {
                StartStopButton.Text = "Start";
                if (t.IsFaulted)
                {
                    MessageBox.Show(t.Exception.ToString());
                }
                RunningTask = null;
            }, uiScheduler);
        }

        bool TryCancelCurrentOperation()
        {
            if (RunningTask != null)
            {
                Cancel.Cancel();
                Cancel = new CancellationTokenSource();
                return true;
            }

            return false;
        }

        void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
        {
            MessageBox.Show(e.Exception.ToString());
            e.SetObserved();
        }
    }
}
Posted On Friday, December 02, 2011 12:24 PM | Feedback (4)