为什么使用Task.Run()仍然阻止我的UI?

时间:2016-12-20 02:55:18

标签: c# async-await

我有一个我用Task.Run()调用的同步方法,我的UI被阻止且没有响应。该方法通过COM Interop从数据库加载信息,我对此没有任何控制。

public List<EdmAddInInfo2> GetInstalledAddins()
{
    IEdmAddInMgr7 addinMgr = m_vault as IEdmAddInMgr7;
    Array installedAddins = Array.CreateInstance(typeof(EdmAddInInfo2), 0);
    addinMgr.GetInstalledAddIns(out installedAddins);
    if (installedAddins?.Length > 0)
        return installedAddins.OfType<EdmAddInInfo2>().ToList();
    return null;
}

我在显示表单时以这种方式调用方法;

private async void LicensesForm_Shown(object sender, EventArgs e)
{        
    var m_addins = await GetInstalledAddins().ConfigureAwait(false);
    toolStripStatusLabel2.Text = $"Loaded {m_addins.Count} addins.";
}

private async Task<List<EdmAddInInfo2>> GetInstalledAddins()
{
    AddinManager addinMgr = new AddinManager(Vault);
    var addins = await Task.Run(() => addinMgr.GetInstalledAddins()).ConfigureAwait(false);
    return addins;
}

通常我会使用BCW并且在路上,但我想我会给任务一个机会。有什么想法吗?

2 个答案:

答案 0 :(得分:3)

在评论讨论之后,我认为有更深层次的涉及COM的事情。涉及COM时要注意的一件事是它使用Dispatcher来接收事件,这些事件之前已经让我感到困惑。如果问题与COM有关,那么可能需要更多有关正在发生的事情的信息以及挖掘可能不值得。我希望我能提供更多帮助,但我认为我必须默认提供简单的方法。启动线程以调用GetInstalledAddins,将结果分配给局部变量,并通过Dispatcher通知UI完成。

另外,从编辑之前的原始答案添加上面的内容,

var m_addins = await GetInstalledAddins().ConfigureAwait(false);

应该是:

var m_addins = await GetInstalledAddins().ConfigureAwait(true);

这是因为在下一行中您指定了UI元素的Text属性。此分配必须从UI线程完成,该线程在您调用GetInstalledAddins()时处于活动状态,但是因为您随后调用ConfigureAwait(false),在await之后,在异步管理器的任何线程上继续执行(我忘记了)被选中的是什么。

在UI代码中使用async / await的优势之一是执行可以(在正常情况下)在进行await调用的同一线程上恢复。这样,您可以在await之后继续访问UI对象。但是,您对ConfigureAwait(false)的调用指示async / await引擎您不关心执行恢复的线程(但在这种情况下,您真的应该关心执行恢复在同一个线程上)。

答案 1 :(得分:1)

  

该方法通过COM Interop从数据库加载信息,我对此无法控制。

那么,根据该方法的实现,可能可能取消阻止UI线程。

但是,您可以尝试这样做:如果该类型在其构造函数中分配COM对象,则它们可能与UI线程绑定。我会尝试在后台线程上创建实例:

private Task<List<EdmAddInInfo2>> GetInstalledAddins()
{
  return Task.Run(() => new AddinManager(Vault).GetInstalledAddins());
}
  

通常我会使用BCW并在路上

BackgroundWorker会遇到完全相同的问题。