任务工作流程序列错误

时间:2010-12-10 01:58:25

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

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

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

有人可以帮助我做到这一点吗?

干杯,
Berryl

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

2 个答案:

答案 0 :(得分:3)

以下更新
<击> 此代码看起来不像我向TPL保证。看起来可能很适合用于BackgroundWorker!

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

更新

所以我显然错过了一些代码,所以这里是修改后的答案。首先,Nicholas是正确的 - 。继续返回一个新任务(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() - 添加到ContinueWith或其他内容中,这样您就不必调用Wait()并阻止UI线程。

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

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

答案 1 :(得分:2)

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

希望有所帮助!