使用任务返回值

时间:2016-05-29 21:22:35

标签: c# multithreading active-directory async-await task-parallel-library

我花了将近两天的时间阅读async / await教程和Stackoverflow上的答案,并尝试了解C#中的异步和并行执行。我仍然无法使用我的代码。

我需要什么

通过PrincipalSearcher异步执行Active Directory搜索,而不会阻止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*/
    {
        ...
    }
}

_activeDirectory 的类:

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

问题

  • await似乎不等待任务完成。在等待行之后searchTask.IsCompleted是正确的,但它不可能是因为它几乎没有时间完成,如果我同步运行它,搜索大约需要5秒。
  • 如果在foreach循环的开头抛出异常:
  

发生System.InvalidCastException HResult = -2147467262
  Message =无法转换类型为&#39; System .__ ComObject&#39;的COM对象。至   界面类型&#39; IDirectorySearch&#39;。此操作失败,因为   QueryInterface在具有IID的接口的COM组件上调用   &#39; {109BA8EC-92F0-11D0-A790-00C04FD8D5A8}&#39;由于以下原因而失败   错误:不支持此类接口(HRESULT异常:0x80004002   (E_NOINTERFACE))。 Source = System.DirectoryServices StackTrace:          在System.DirectoryServices.SearchResultCollection.get_SearchObject()
  的InnerException:

思想

  • 我发现这种异常与无效的SynchronizationContext有关,但我不知道这可能发生在这里。我还在几行代码上打印了Thread.CurrentThread.ManagedThreadId,它总是返回相同的id。
  • 我还读到异步方法不应该返回void而是Task<T>,但我不认为这是相关的,因为没有人异步使用SearchButtonClick()。 Task.Run()可能会返回一个任务,如果它甚至相关。整个主题对我来说仍然模糊不清。

问题

  • 为什么await不等待任务完成?
  • 例外的原因是什么?

3 个答案:

答案 0 :(得分:3)

; 等待Task完成。 await模式和TPL库唯一遗漏的是async\await被抛出内部 Task在那一刻没有被抛出。它被缓存在Exception的{​​{3}}属性中,并且在您想要得到它的结果之后被抛出。

所以问题是你在Exception内运行的代码无法运行到另一个线程,正如@NineBerry所说。您必须在Task内创建Task - 对象 ,如下所示:

AD

或者,您可能必须每次为您的主题创建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)); } 变量。

答案 1 :(得分:2)

您不能在另一个线程上使用_activeDirectory而不是您创建对象的位置。

这是基于实现对象内部使用的COM主机的方式。某些COM主机以某种方式实现,以便它们可以在多个线程上使用,其中一些以某种方式实现,以便对象只能在创建它的同一线程上使用。

您需要更改代码以在_activeDirectory中执行的代码中创建Task.Run或更改_activeDirectory的实现以创建用于访问内部活动目录的COM对象搜索方法。

您还需要确保该帖子具有ApartmentState ApartmentState.STA。有关如何执行此操作,请参阅this question

由于异常,对等待的调用立即返回。如果任务中发生未处理的异常,则该任务在此时完成。

执行搜索并在同步执行时占用5秒,因为在与创建它的位置相同的线程中使用COM对象时不会发生异常,如上所述。

答案 2 :(得分:0)

即使在与Active Directory搜索相同的线程上创建新的UserModel之后,我也遇到了此异常。问题是IEnumerable<UserModel>包含主线程不知道的嵌套类型UserPrincipal。我可以使用.ToList()来解决此问题,该方法删除了嵌套类型。

_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
            });
    }
}