InvalidCastException when using Task return value

4

I have spend almost two days reading async/await tutorials and answers on Stackoverflow and trying to understand asynchronous and parallel execution in C#. Still I can not get it to work with my code.

What I need

Execute a Active Directory search through PrincipalSearcher asynchronously without blocking the WPF UI.

Implementation

protected async void SearchButtonClick()
{
    Task<PrincipalSearchResult<Principal>> searchTask = Task.Run(() => _activeDirectory.FindGroup(searchText.Text));

    PrincipalSearchResult<Principal> searchResult = await searchTask;

    foreach (var foundGroup in searchResult) /*exception thrown here*/
    {
        ...
    }
}

Class of _activeDirectory:

public PrincipalSearchResult<Principal> FindGroup(String pattern)
{
    ...
    PrincipalSearchResult<Principal> searchResult = searcher.FindAll();
    return searchResult;
}

Problem

  • await does not seem to wait for the task to complete. searchTask.IsCompleted is true after the await line, but it can't be because it takes almost no time to be completed and if I run it synchronously the search takes about 5s.
  • An exception if thrown at the beginning of the foreach loop:

System.InvalidCastException occurred HResult=-2147467262
Message=Unable to cast COM object of type 'System.__ComObject' to interface type 'IDirectorySearch'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{109BA8EC-92F0-11D0-A790-00C04FD8D5A8}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)). Source=System.DirectoryServices StackTrace: at System.DirectoryServices.SearchResultCollection.get_SearchObject()
InnerException:

Thoughts

  • What I found is that this kind of exception is related to invalid SynchronizationContext but I don't see how this could have happened here. I also printed Thread.CurrentThread.ManagedThreadId on several lines of codes and it always returned the same id.
  • I also read that async methods should not return void but Task<T> but I don't think this is relevant here because nobody is using SearchButtonClick() asynchronously. And Task.Run()probably does return a Task if that even relevant. The whole subject is still foggy to me.

Question

  • Why does await not wait for the Task to complete?
  • What is the reason for the exception?
c#
multithreading
active-directory
async-await
task-parallel-library
asked on Stack Overflow May 29, 2016 by mrplow • edited May 29, 2016 by VMAtm

3 Answers

3

await does wait for the Task to complete. The only thing you've missed with async\await pattern and TPL library is that the Exception being thrown inside the Task isn't being thrown right in that moment. It's being cached inside the Exception property of the Task and is being thrown right after you want to get th result of it.

So the problem is that the code you're running inside the Task, can't be run into another thread, as @NineBerry said. You have to create your AD-object inside the Task, something like this:

Task<PrincipalSearchResult<Principal>> searchTask = Task.Run(() =>
{
    // this have to be a local variable inside your task
    var _activeDirectory = GET_THE_AD();
    return _activeDirectory.FindGroup(searchText.Text));
}

or, may be, you have to create the searcher variable each time for your thread.

answered on Stack Overflow May 29, 2016 by VMAtm
2

You cannot use _activeDirectory on another thread than where you create the object.

This is based on the way the COM host used inside your object is implemented. Some COM hosts are implemented in a way so that they can be used on multiple threads, some are implemented in a way so that an object can only be used on the same thread where it was created.

You need to change your code to create _activeDirectory within the code you execute in Task.Run or change the implementation of _activeDirectory to create the COM object it used to access the active directory inside the search method.

You also need to make sure, that the thread has the ApartmentState ApartmentState.STA. See this question on how to do that.


The call to await immediately returns because of the exception. If an unhandled exception occurs within a task, the task is finished at that moment.

The search is executed and takes 5s when executed synchronously because the exception does not occur when using the COM object in the same thread as where it was created, as explained above.

answered on Stack Overflow May 29, 2016 by NineBerry • edited May 23, 2017 by Community
0

I ran into this exception even after creating a new UserModel on the same thread as the Active Directory search. The problem was that IEnumerable<UserModel> contained a nested type UserPrincipal which the main thread did not know about. I was able to resolve this by using .ToList() which removed the nested type.

_context = new PrincipalContext(ContextType.Domain);

public async Task<IEnumerable<UserModel>> SearchDisplayNameAsync(string searchPhrase, bool enabled = true)
{
    IEnumerable<UserModel> results = await Task.Run(() => SearchDisplayName(searchPhrase: searchPhrase, enabled: enabled));
    return results.ToList();  // <-- ToList() removes nested type
}

public IEnumerable<UserModel> SearchDisplayName(string searchPhrase, bool enabled = true)
{
    UserPrincipal userPrincipal = new UserPrincipal(_context)
    {
        DisplayName = $"{searchPhrase}*",
        Enabled = enabled
    };

    using (PrincipalSearcher searcher = new PrincipalSearcher(userPrincipal))
    {
        return searcher.FindAll()
            .OfType<UserPrincipal>()
            .Select(u => new UserModel
            {
                Guid = (Guid)u.Guid,
                DisplayName = u.DisplayName,
                EmailAddress = u.EmailAddress
            });
    }
}
answered on Stack Overflow Oct 19, 2018 by manotheshark

User contributions licensed under CC BY-SA 3.0