【问题标题】:Why would a long running Task still block the UI?为什么长时间运行的任务仍然会阻塞 UI?
【发布时间】:2012-06-06 21:42:32
【问题描述】:

我正在尝试解决我的 UI 被阻止但我不明白原因的问题。

public Task AddStuff(string myID, List<string> otherIDs)
{
    Action doIt = () =>
    {
        this.theService.AddStuff(myID, otherIDs);
    };

    return Task.Factory.StartNew(doIt, TaskCreationOptions.LongRunning);
}

如果列表很长,调用可能需要 30 秒,并且整个应用程序变得无响应(在 Windows 7 中变成白色)。

有没有其他方法可以做到这一点,这样它就不会阻塞 UI?


编辑

好的,所以有很多关于此的代码,我将尝试保持相关性。我确实意识到回到原始代码,我已经删除了一些可能很重要的东西。我是否应该使用与TaskScheduler.Current 不同的TaskScheduler?

此外,没有阻止任何此代码的等待语句,并且服务不与 UI 交互。

Task.Factory.StartNew(objState =>
    {
        LoadAssets(objState);
    }, state, this.cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);

private void LoadAssets(object objState)
{
    LoadAssetsState laState = (LoadAssetsState)objState;

    List<string> assetIDs = new List<string>();

    for (int i = 0; i < laState.AddedMediaItems.Count; i++)
    {
        if (laState.CancellationToken.IsCancellationRequested)
            return;

        string assetId = this.SelectFilesStep.AssetService.GetAssetId(laState.AddedMediaItems[i], laState.ActiveOrder.OrderID);

        assetIDs.Add(assetId);

    }

    if (laState.CancellationToken.IsCancellationRequested)
        return;

    this.ApiContext.AddAssetToProduct(laState.ActiveOrder.OrderID, laState.ActiveProduct.LineID, assetIDs, laState.Quantity, laState.CancellationToken).ContinueWith(task =>
    {
        if (laState.CancellationToken.IsCancellationRequested)
            return;


        App.ApiContext.GetOrderDetails(laState.ActiveOrder.OrderID, false, laState.CancellationToken).ContinueWith(orderDetailsTask =>
        {
            if (laState.CancellationToken.IsCancellationRequested)
                return;

            this.activeOrder = orderDetailsTask.Result;

            this.StandardPrintProductsStep.Synchronize(this.activeOrder);

        });
    });
}

public Task AddAssetToProduct(string orderID, string lineID, List<string> assetIDs, int quantity, CancellationToken? cancellationToken = null)
{
    Action doIt = () =>
    {
        if (cancellationToken.IsCancellationRequested())
            return;

        this.ordersService.AddAssetToProduct(orderID, lineID, assetIDs, quantity);
    };

    if (cancellationToken != null)
        return Task.Factory.StartNew(doIt, cancellationToken.Value, TaskCreationOptions.LongRunning, TaskScheduler.Current);
    else
        return Task.Factory.StartNew(doIt, TaskCreationOptions.LongRunning);
}

编辑

我在服务调用之前和之后放置了断点,它是阻塞 UI 的服务调用,而不是任何其他行。

听起来没有理由阻止它,所以我想如果它很长,我会分解列表并进行多次调用。我只是想确保我的任务逻辑没有遗漏任何内容。

【问题讨论】:

  • 根本原因不在显示的代码中。可以显示 AddStuff 的调用者吗?
  • this.theService.AddStuff(myID, otherIDs); 是否与public Task AddStuff(string myID, List&lt;string&gt; otherIDs) 相同,如果是这样,那么由于无限递归,您最终会创建无限数量的任务!
  • 当你准备好全力以赴时,为什么不打UI。而不是打几个小拳。这些小动作也会使您的 UI 变得响应。
  • 这是更新进度条,但直到最后的服务调用,UI 才被阻塞。
  • 我会通过将其分解为多个步骤来弄清楚为什么这会阻塞 UI 线程而不是“踢”。例如,让操作 1) 改为执行 Thread.Sleep 并 2) 打印线程 id 以查看它是否为“1” - 听起来 TaskScheduler.Default 在这里是更好的选择?

标签: c# wpf task-parallel-library


【解决方案1】:

有没有其他方法可以做到这一点,这样它就不会阻塞 UI?

此调用本身不应阻塞 UI。但是,如果 theService.AddStuff 与 UI 的 SynchronizationContext 进行了一些同步,这可能会导致 UI 被该调用有效地阻塞。

否则,问题可能发生在此函数之外。例如,如果您对该方法返回的任务调用Wait(),在UI线程中,UI线程将被阻塞,直到完成。


您可能想使用TaskScheduler.Default,而不是TaskScheduler.Current。如果这是在基于 UI 线程的 TaskScheduler 上调度的任务中调用的,它将在 UI 线程上自行调度。

【讨论】:

  • 没有骰子,使用 Default 而不是 Current 的行为方式相同
  • @JoshRusso 你能在 Visual Studio 的并发可视化工具下运行它吗?您可以通过这种方式打破并准确显示阻塞主线程的内容...
  • @JoshRusso 否-它在分析窗口中-启动性能向导(VS 2010)-在 VS 2012 中它是分析->并发-> ...仅在更高的 SKU 中。您也可以只查看线程窗口,但如果线程被阻塞,它并不总是能正常工作。
  • 是的,我认为我们没有足够高的版本。
  • 此外,我们实际上将在下周提交代码,我非常确信是他们的 API 导致了阻塞。所以我只是给了它一个创可贴,将列表拆分并进行一系列较小的调用。
【解决方案2】:

希望我可以将格式化的代码放在评论中,但由于我不知道如何,所以添加这个 sn-p 作为答案。这是我用来确定任务是否在 UI 线程上运行的方法(因为您不希望它运行)并使操作完全不同(简单的 thread.sleep)。

var state = new object();
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;

var task = Task.Factory.StartNew(
    objState => { Console.WriteLine ("Current thread is {0}", Thread.CurrentThread.ManagedThreadId); Thread.Sleep(30); },
    state,
    cancellationToken,
    TaskCreationOptions.LongRunning,
    TaskScheduler.Current);

task.Wait();    

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-10-31
    • 2014-10-20
    • 1970-01-01
    • 2017-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-20
    相关资源
    最近更新 更多