【问题标题】:Wait for child task to really compelete等待子任务真正完成
【发布时间】:2015-09-21 12:56:11
【问题描述】:

我有以下代码作为子任务开始,以获取给定目录中的所有文件并在主题上执行某些操作并为每个文件调用事件以提醒父任务:

internal class FileFinder
{
    private readonly string _fileFormat;

    public delegate void FileFoundDelegate(string filePath);

    public event FileFoundDelegate OnFileFound;

    public FileFinder(string fileFormat)
    {
        _fileFormat = fileFormat;
    }

    public bool Start(CancellationToken cancellationToken, string directory)
    {
        try
        {
            if (OnFileFound == null)
                return false;

            var foundedFiles = new ThreadLocal<IEnumerable<string>>();
            try
            {
                foundedFiles.Value = Directory.EnumerateFiles(directory, _fileFormat, SearchOption.AllDirectories)
                    .AsParallel();
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Parallel : " + ex.Message);
            }

            foreach (var file in foundedFiles.Value)
            {
                if (cancellationToken.IsCancellationRequested)
                    return true;

                // Call file found event with normalized file name
                OnFileFound?.Invoke(file);
            }

            return true;
        }
        catch (Exception ex)
        {
            Common.DebugLog(System.Reflection.MethodBase.GetCurrentMethod().Name,
                ex.InnerException?.Message ?? ex.Message);
            return false;
     }
   }
}

并使用名为 Scatter 的父任务调用它,FileFinder 是主题之一的分散运行 5 个单独任务:

internal class Scatter
    {
        private readonly CancellationToken _cancellationToken;
        private readonly string _directory;
        private readonly string _fileFormat;

        private FileFinder _emailFinder;

        public Scatter(CancellationToken cancellationToken, string directory, string fileFormat)
        {
            _cancellationToken = cancellationToken;
            _directory = directory;
            _fileFormat = fileFormat;
        }

        public Task Start()
        {
            try
            {
                return Task.Factory.StartNew(StartProc,
                    TaskCreationOptions.AttachedToParent | TaskCreationOptions.LongRunning);
            }
            catch (Exception)
            {
                return null;
            }
        }

        private void StartProc()
        {
            try
            {
                // Find pdf files
                _emailFinder = new FileFinder(_fileFormat);
                _emailFinder.OnFileFound += FileFound;
                Task.Factory.StartNew(() => _emailFinder.Start(_cancellationToken, _directory),
                    TaskCreationOptions.AttachedToParent | TaskCreationOptions.LongRunning);
            }
            catch (Exception ex)
            {
                Common.DebugLog(System.Reflection.MethodBase.GetCurrentMethod().Name, ex.InnerException.Message);
            }
        }

        private void FileFound(string filePath)
        {
            Debug.WriteLine("File Found");
        }
    }

最后一个主任务为每个目录运行单独的分散:

internal class Master
    {
        private readonly CancellationToken _cancellationToken;

        internal delegate void ParseFinish();
        public event ParseFinish OnParseFinish;

        public Master(CancellationToken cancellationToken)
        {
            _cancellationToken = cancellationToken;
        }

        public bool Start(List<string> targetDirectories, string fileFormat)
        {
            try
            {
                Task.Factory.StartNew(() => StartProc(targetDirectories, fileFormat), _cancellationToken);
                return true;
            }
            catch (Exception ex)
            {
                Common.DebugLog(System.Reflection.MethodBase.GetCurrentMethod().Name,
                    ex.InnerException?.Message ?? ex.Message);
                return false;
            }
        }

        private bool StartProc(List<string> directories, string fileFormat)
        {
            try
            {
                List<Task> targetScatterList = new List<Task>();

                foreach (string dir in directories)
                {
                    var scatter = new Scatter(_cancellationToken,dir, fileFormat);

                    targetScatterList.Add(scatter.Start());
                }

                // Wait for finish all tasks & call parse finish event
                Task.WaitAll(targetScatterList.ToArray());
                OnParseFinish?.Invoke();

                return true;
            }
            catch (Exception ex)
            {
                Common.DebugLog(System.Reflection.MethodBase.GetCurrentMethod().Name,
                    ex.InnerException?.Message ?? ex.Message);
                return false;
            }
        }
    }

我有主任务等待所有目录的任务完成并且不涉及应用程序主线程。

像这样从主线程调用主任务:

    List<string> directoryList = ListBox1.Items.Cast<string>().ToList();

    // Create cancelation token
    _cancellationTokenSource = new CancellationTokenSource();
    _cancellationToken = _cancellationTokenSource.Token;

    // Start master task that populate new task for each target
    var masterTask= new Master(_cancellationToken);
    masterTask.OnParseFinish += ParseFinish;
    masterTask.Start(directoryList, tbFileFormat.Text);

我在示例图书目录中有 287,198 个 PDF 文件,FileFound 事件在不同的项目运行(287170、287182、287146 等)中称为随机时间,并且不会迭代所有已创建的项目。

在小文件列表中它并没有显示出很大的差异

我认为父任务完成,子任务立即杀死。

有什么想法吗?

谢谢。

【问题讨论】:

  • 只是为了它,你能做一个Console.WriteLine(foundedFiles.Value.Length) 以确保枚举本身是完整的吗?
  • 您在StartProc 方法中启动了一项任务,而不是等待它。您提到您的主线程正在等待父任务,但您的父任务没有等待子任务。
  • @EdT 不正确 - 如果子任务以 AttachedToParent 标志启动,则等待父任务将仅在所有附加子任务完成后返回。
  • 无法重现所描述的行为。对我来说,提供的代码会遍历所有找到的 pdf 文件。
  • 您在异常处理中是从哪里学到的?即使只是做catch (Exception ex) 已经够糟糕了,但是到处重复它是糟糕的编码。你到处隐藏错误。

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


【解决方案1】:

您的代码是我见过的最惊人的过度编码之一。

这是使用 Microsoft 的响应式框架 (NuGet "Rx-Main") 编写的相同代码。

var query =
    from dir in directoryList.ToObservable()
    from file in
            Directory.EnumerateFiles(dir, tbFileFormat.Text, SearchOption.AllDirectories)
    select file;

var subscription = query.Subscribe(file =>
{
    ParseFinish(file);
});

就是这样。这一切都使用后台线程处理。它摆脱了所有这些类,只做你需要的工作。

如果您想中途取消,只需这样做:

subscription.Dispose();

如果您想知道何时完成,只需这样做:

var subscription = query.Subscribe(file =>
{
    ParseFinish(file);
}, () =>
{
    /* Handle the query is finished here */
});

【讨论】:

  • 非常感谢,但我也想知道我的代码的问题。
  • 我在 Scatter 中有几个任务,FileFinder 是主题之一,我想手动控制所有操作。
  • @MojtabaTajik - 我认为您现有代码的问题是Task.Factory.StartNew(() =&gt; StartProc(targetDirectories, fileFormat), _cancellationToken) 行。您正在开始一项新任务,但您没有对返回的任务对象做任何事情。相反,您会立即返回 bool
  • @MojtabaTajik - 你想“手动控制所有操作”是什么意思?
  • 你是对的,问题是那行代码,如果我在最后添加一个 Wait() ,所有问题都解决了,但我的主线程参与并且 UI 冻结直到所有任务完成,我添加主任务不涉及我的主线程!
猜你喜欢
  • 2013-12-18
  • 2016-01-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多