在第二次打开Task.Factory.StartNew后,我的所有UI都冻结了

时间:2014-04-21 11:24:01

标签: c# .net winforms task-parallel-library task

我有接收文件列表的功能,并且可以在单个/单独的线程中工作:

public void DoWork(params...)
{
    _filesInProcess = 0;
    _filesFinished = 0;
    _tokenSource = new CancellationTokenSource();
    var token = _tokenSource.Token;
    Task.Factory.StartNew(() =>
    {
        try
        {
            Parallel.ForEach(_indexedSource,
                new ParallelOptions
                {
                    MaxDegreeOfParallelism = parallelThreads //limit number of parallel threads 
                },
                file =>
                {
                    if (token.IsCancellationRequested)
                        return;
                    //do work...
                });
        }
        catch (Exception)
        { }

    }, _tokenSource.Token).ContinueWith(
            t =>
            {
                //finish...
                 if (OnFinishWorkEventHandler != null)
                     OnFinishWorkEventHandler(this, EventArgs.Empty);
            }
        , TaskScheduler.FromCurrentSynchronizationContext() //to ContinueWith (update UI) from UI thread
        );
}

我添加了在循环中运行此命令的选项,在此函数完成后从主窗体运行,如果我需要另一个循环,我再次调用此函数,但在第二次冻结我的所有UI,我无法找到为什么

这就是我打电话给我的功能:

private void StartJob()
{
    string[] files = GetlFiles().ToArray();
    Job job = new Job(files);
    timerStatus.Enabled = true;
    job.OnStartPlayEventHandler += job_OnStartPlayEventHandler;
    job.OnFinishPlayEventHandler += job_OnFinishPlayEventHandler;
    job.OnFinishWorkEventHandler += job_OnFinishWorkEventHandler;
    job.StartTimerEventHandler += job_StartTimerEventHandler;
    job.StopTimerEventHandler += job_StopTimerEventHandler;
    job.DoWork(
                NetworkAdapter.SelectedAdapter.PacketDevice,
                ReadSpeed(),
                PlayOption.Regular,
                ChecksumFixer.FixBadChecksum,
                ReadParallelThreads(PlayState.Single),
                Iteration.Loops
            );
}

此操作完成后:

private void job_OnFinishWorkEventHandler(object sender, EventArgs e)
{
    Job job = sender as Job;
    job.OnStartPlayEventHandler -= job_OnStartPlayEventHandler;
    job.OnFinishPlayEventHandler -= job_OnFinishPlayEventHandler;
    job.OnFinishWorkEventHandler -= job_OnFinishWorkEventHandler;
    job.StartTimerEventHandler -= job_StartTimerEventHandler;
    job.StopTimerEventHandler -= job_StopTimerEventHandler;
    Iteration.LoopFinished++;

    if (Iteration.LoopFinished < Iteration.Loops) // In case i want another loop
        StartJob();
    else
    {
        UnlockButtonsAfterPlay();
        UnlockContextMenuAfterPlay();
    }
}

正如我提到的所有陷入第二次迭代

1 个答案:

答案 0 :(得分:5)

Task.Factory.StartNew is dangerous。它使用TaskScheduler.Current而不是TaskScheduler.Default

在您的情况下OnFinishWorkEventHandlerContinueWith触发,该TaskScheduler.FromCurrentSynchronizationContext()TaskScheduler.Current的UI线程中运行。所以此时TaskScheduler.Default是UIScheduler而不是Task.Run(线程池调度程序)。 这就是为什么你的第二个任务在UI线程中安排,导致UI冻结。

要解决此问题,请使用始终指向TaskScheduler.Default的{​​{1}}。 Task.Run是.net 4.5中的新功能,如果您使用.net 4.0,则可以使用默认参数创建TaskFactory,然后就可以使用它。

private static readonly TaskFactory factory = new TaskFactory(CancellationToken.None,
        TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default);

然后使用

factory.StartNew(...);

如果您只是在自己不需要创建工厂实例时才使用它,只需明确指定TaskScheduler即可。

Task.Factory.StartNew(() =>{
   //Your code here
}, _tokenSource.Token,
   TaskCreationOptions.None,
   TaskScheduler.Default)//Note TaskScheduler.Default here
.ContinueWith(
        t =>
        {
            //finish...
             if (OnFinishWorkEventHandler != null)
                 OnFinishWorkEventHandler(this, EventArgs.Empty);
        }
    , TaskScheduler.FromCurrentSynchronizationContext());