1

I have an Excel workbook object containing one sheet, that I want to copy its content into a List.

I have this method:

private Task GeneratePagesList()
{                        
    _pages = new List<Model.Page>();                                                    
    short idCount = 0;

    var generatePagesListTask = new Task(() =>
        {                                                 
            _pages.Add(new Model.Page()
            {
                Url = new Uri(_worksheetRange.Cells[i, j].Value2.ToString(), 
                UriKind.RelativeOrAbsolute),
                Id = idCount
            });
        });
    return generatePagesListTask;
}

Now I want to consume this method and the Task that it returns as follows:

public async void ConvertExelDataAsync()
{                       
    var generatePagesListTask = GeneratePagesList();
    generatePagesListTask.Start();
    await generatePagesListTask;
}

When I run, The operation takes too long, and it never quits the ConvertExelDataAsync method, after a little while (that apparently is 60 sec), I receive an Exception that says:

Managed Debugging Assistant 'ContextSwitchDeadlock' has detected a problem in 'C:\Users\Aymen\Documents\Visual Studio 2013\Projects\WebGraphMaker\WebGraphMaker\bin\Debug\WebGraphMaker.vshost.exe'.

Additional information: The CLR has been unable to transition from COM context 0xd33a5e78 to COM context 0xd33a5fa0 for 60 seconds. The thread that owns the destination context/apartment is most likely either doing a non pumping wait or processing a very long running operation without pumping Windows messages. This situation generally has a negative performance impact and may even lead to the application becoming non responsive or memory usage accumulating continually over time. To avoid this problem, all single threaded apartment (STA) threads should use pumping wait primitives (such as CoWaitForMultipleHandles) and routinely pump messages during long running operations.

Notice: This is the first time I interact with Com objects.

Update 1 :

The Excel consumption works just fine when it's not in a task, once in a task, and the task starts, the problem occurs !

Update 2 : when debugging, once the debugger reaches the line

int rowCount = _worksheetRange.Rows.Count;

It exits and nothing happen, can't explain it.

Update 3 : After opening the Debug>Windows>Threads, it shows this :

enter image description here

The Convert method calls all what's above, defined like this :

public static async void Convert()
        {

            var excelDataConverter = new ExcelDataConverter(ExcelDataReader.ReadData());
            excelDataConverter.ConvertExelDataAsync();
        }

enter image description here

AymenDaoudi
  • 7,811
  • 9
  • 52
  • 84
  • 2
    It is called "deadlock". Caused by you trying to use a single-threaded object in a worker thread. And doing something else we cannot see, like hanging up your main thread so the call cannot complete. Use Debug + Windows + Threads to find out where your main thread is stuck, the one that owns the worksheet. – Hans Passant May 03 '14 at 17:45
  • @HansPassant : Just opened Debug>Windows>Threads it doesn't show anything, the thread window is just empty. Maybe I should mention as in the `Update2` that everything exits at that line, any breakpoint after that won't catch anything, does that mean it's stuck there ? And in the caller method `ConvertExelDataAsync` it stucks at the last line. What should I do, thx. – AymenDaoudi May 03 '14 at 17:58
  • Use Debug + Break All first. – Hans Passant May 03 '14 at 18:04
  • @HansPassant: The yellow arrow points to the Main Thread. I'll Update my question and add info . – AymenDaoudi May 03 '14 at 18:13
  • It is certainly deadlocked like a rock. Stuck in the ReadLine() call. Entirely illegal for an STA thread, like the debugger told you, blocking such a thread requires pumping. Delete the [STAThread] attribute from your Main() method for a quick fix. – Hans Passant May 03 '14 at 23:56
  • 1
    @AymenDaoudi, what kind of app is this (console, WPF/WinForms, ASP.NET, service)? Why is `GeneratePagesList` declared `void` in your code, yet you return `generatePagesListTask` from it? – noseratio May 04 '14 at 00:05
  • @HansPassant : I tried to delete the [STAThread], That raised a ThreadStateException saying that I should put back the [STAThread], and that this exception is raised only when the debugger is attached to the process – AymenDaoudi May 04 '14 at 12:27

2 Answers2

3

To add to @StepehCleary's answer, the message itself is quite informative:

To avoid this problem, all single threaded apartment (STA) threads should use pumping wait primitives (such as CoWaitForMultipleHandles) and routinely pump messages during long running operations.

You have a COM proxy to an out-of-proc Excel COM object, the proxy was created on your main thread (probably an STA UI thread). Then you're accessing it on a worker pool thread (which is an MTA thread).

While the COM proxy object itself may be thread-safe for calls from a worker thread like this, under the hood it most likely trying to marshal the call back to the main thread where the proxy was originally created on. That's where the deadlock occurs.

To stay on the safe side, I suggest you create a dedicated STA thread which does pump messages, create all your COM objects on that threads and call out there.

I have two helper classes for this, ThreadAffinityTaskScheduler and ThreadWithAffinityContext, available here, they should work in any execution environment.

Community
  • 1
  • 1
noseratio
  • 59,932
  • 34
  • 208
  • 486
  • @AymenDaoudi, BTW, are you handling any events sourced by Excel COM objects? – noseratio May 04 '14 at 12:39
  • No no I'm not, how does that affect, I'd be glad if you explain (I'm newbie to this) – AymenDaoudi May 04 '14 at 12:46
  • 1
    @AymenDaoudi, it's just that .NET event handlers for events sourced by out-of-proc COM servers may be called on a random thread, regardless of the object creator thread's model. This is different from the classic COM behavior. I posted about that here: http://stackoverflow.com/q/18458398/1768303 – noseratio May 04 '14 at 12:51
2

When you're working with async and await, there are some general best practices. One is returning "hot" (running) tasks, so do not use new Task or call Task.Start; use Task.Run instead. Another is to avoid async void; use async Task instead.

However, the core problem is as @HansPassant pointed out: you are creating an STA COM object (the Excel stuff) and then accessing it from a thread pool thread after blocking the STA thread. This is doomed to fail.

Instead, just remove all the async and Task code, and create your list on the STA thread.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • The remains of my ancient knowledge of COM suggest there's no thing like an out-of-proc STA COM object. The EXE COM server may initialize its threads as STA, but the COM marshaller proxy/stub objects are marked as `Both` and are free-threaded (they can be called from any thread inside the client process, and will marshal the call to the correct STA thread inside the server process). So now I think I might be somewhat wrong with my explanation, but I don't have a better picture. – noseratio May 04 '14 at 12:58