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.
Execute a Active Directory search through PrincipalSearcher asynchronously without blocking the WPF UI.
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;
}
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. 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:
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.await
not wait for the Task to complete?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.
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.
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
});
}
}
User contributions licensed under CC BY-SA 3.0