【问题标题】:Task workflow sequence is wrong任务工作流顺序错误
【发布时间】:2010-12-10 01:58:25
【问题描述】:

使用下面的代码,在最终的 ContinueWith 中进行的最终 UI 更新永远不会发生。我认为这是因为我最后有 Wait() 。

我这样做的原因是因为没有等待,该方法将在后台构建完成之前返回 IDataProvider。

有人可以帮我解决这个问题吗?

干杯,
浆果

        private IDataProvider _buildSQLiteProvider()           
    {

        IDataProvider resultingDataProvider = null;
        ISession session = null;

        var watch = Stopwatch.StartNew();

        var uiContext = TaskScheduler.FromCurrentSynchronizationContext();

        // get the data
        var buildProvider = Task.Factory
            .StartNew(
                () =>
                    {
                        // code to build it
                    });

        // show some progress if we haven't finished
        buildProvider.ContinueWith(
            taskResult =>
            {
                // show we are making progress;
            },
            CancellationToken.None, TaskContinuationOptions.None, uiContext);

        // we have data: reflect completed status in ui 
        buildProvider.ContinueWith(
            dataProvider =>
            {
                // show we are finished;
            },
            CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, uiContext);

        try {
            buildProvider.Wait();
        }

        catch (AggregateException ae)
        {
            foreach (var e in ae.InnerExceptions)
                Console.WriteLine(e.Message);
        }
        Console.WriteLine("Exception handled. Let's move on.");

        CurrentSessionContext.Bind(session);

        return resultingDataProvider;
    }

====

只是为了清楚

我与 ui 线程交谈没有问题。第一个继续更新ui就好了。我遇到的麻烦是最后一次 ui 更新的时间和数据提供者的返回。

我注释掉了一些代码以降低帖子中的噪音水平并专注于任务排序。

====

好的,工作代码

        private void _showSQLiteProjecPicker()           
    {

        var watch = Stopwatch.StartNew();
        var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
        ISession session = null;

        // get the data
        var buildProvider = Task.Factory.StartNew(
                () =>
                {
                    var setProgress = Task.Factory.StartNew(
                        () =>
                        {
                            IsBusy = true;
                            Status = string.Format("Fetching data...");
                        },
                        CancellationToken.None, TaskCreationOptions.None, uiScheduler);

                    var provider = new SQLiteDataProvider();
                    session = SQLiteDataProvider.Session;
                    return provider;
                });


        buildProvider.ContinueWith(
            buildTask =>
                {
                    if(buildTask.Exception != null) {
                        Console.WriteLine(buildTask.Exception);
                    }
                    else {
                        Check.RequireNotNull(buildTask.Result);
                        Check.RequireNotNull(session);

                        _updateUiTaskIsComplete(watch);

                        CurrentSessionContext.Bind(session);
                        var provider = buildTask.Result;
                        var dao = provider.GetActivitySubjectDao();
                        var vm = new ProjectPickerViewModel(dao);
                        _showPicker(vm);
                    }
                },
            CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, uiScheduler);
    }

【问题讨论】:

    标签: c# asynchronous task task-parallel-library


    【解决方案1】:

    以下更新
    这段代码看起来不像我需要 TPL。看起来可能对 BackgroundWorker 很有用!

    无论哪种方式,更新都可能不会发生,因为您无法从单独的线程更新 UI - 您需要在 UI 线程上运行更新。您应该为此使用 Dispatcher(http://stackoverflow.com/questions/303116/system-windows-threading-dispatcher-and-winforms 包含 WPF 和 WinForms 的信息)

    更新:

    所以我显然错过了一些代码,所以这里有一个修改后的答案。首先,Nicholas 是正确的—— .ContinueWith 返回一个新任务 (http://msdn.microsoft.com/en-us/library/dd270696.aspx)。所以不是

    var result = Task.Factory.StartNew(...);
    result.ContinueWith(...);
    

    您可能想要创建一个新任务,然后进行所有ContinueWith() 调用并分配给该任务,然后对该任务调用.Start()。比如:

    var task = new Task(...).ContinueWith(...);
    task.Start();
    

    然而,一开始的设计就有一个缺陷(在我看来)!您正在尝试异步运行此代码,这就是您使用线程和 TPL 的原因。但是,您在 UI 线程上调用 buildProvider.Wait(); 会阻塞 UI 线程,直到此任务完成!除了在 UI 线程被阻塞时在 ContinueWith() 中重新绘制 UI 的问题之外,这里的多线程没有任何好处,因为您正在阻塞 UI 线程(一个主要的禁忌)。您可能想要做的是将Bind()-ing 粘贴在 ContinueWith 或其他内容中,这样您就不必调用 Wait() 并阻止 UI 线程。

    我的 0.02 美元是,如果您希望查询花费很长时间,那么您真正想要的是 2 个线程(或 TPL 中的任务)——一个用于执行查询,一个用于每隔一段时间更新 UI 和状态。如果您不希望花费这么长时间,我认为您只需要一个线程(任务)来查询,然后在完成后更新 UI。我可能会通过BackgroundWorker 做到这一点。 TPL 是为管理大量任务和延续等而构建的,但对于这种事情似乎有点过分——我认为你可以使用 BackgroundWorker 用更少的代码来完成它。但是你提到你想使用 TPL,这很好,但你将不得不对其进行一些修改,以便它真正在后台运行!

    PS - 您可能打算将 Console.WriteLine("Exception handled. Let's move on."); 放在 catch

    【讨论】:

    • BGW 带来了自己的一系列问题,但我同意这种情况可以。我确实怀疑,一旦我掌握了 TPL 的窍门,它会变得更容易、更灵活,而且这个问题是关于 TPL 的使用,而不是如何报告一般的进度。更重要的是,如果您查看代码,我正在捕获 ui 上下文而不依赖 WPF(一件好事),并且第一个 ContinueWith *does" 更新了 ui。干杯
    • @Statis。好的,我按照我想要的方式进行了这项工作。是的,最后的等待是失败者,第一个更新进度的 ContinueWith 也是如此。不过,最后一个 ContinueWith 在概念上是正确的。而不是第一个 ContinueWith,它确实需要是一个新任务(正如你和 Nicholas 建议的那样)但它必须嵌套在原始任务中,并且它必须具有 UI 线程。除了想学习 TPL 和标准化我的异步编程方法之外,我最终还想在这里捎带其他任务,这对 BGW 来说会很痛苦。谢谢你的帮助。干杯
    【解决方案2】:

    我有点模糊,但上次我使用 TPL 时发现它很混乱。 ContinueWith() 返回一个新的 Task 实例。因此,您需要将第二个ContinueWith() 结果分配给一个新变量,例如var continuedTask = builderProvider.ContinueWith(...),然后将最后一个更改为引用continuedTask.ContinueWith() 而不是buildProvider.ContinueWith()。然后Wait() 上最后一个Task

    希望有帮助!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多