TemplateManager,记录一份失败的代码

版权声明:本文为starfd原创文章,转载请标明出处。 https://blog.csdn.net/starfd/article/details/80266305

Spire.Pdf这个控件应该蛮多需要操作pdf的开发都多少接触过,其标榜免费的版本FreeSpire.Pdf,按其官方声明,也仅仅比商业版多了一些限制,但其实还隐藏性的做了一些其他限制,而其声明中根本没提及的不支持多线程并发操作,恰恰是导致了标题描述的失败代码产生!

先说下使用场景,业务需要按模板批量生成PDF文件,在最初使用Spire.Pdf进行单个文件生成测试时,一切都很顺利,而当测试多线程并发操作时,因为使用的是同一份PDF模板,结果程序直接报“文件被占用”异常,这还算在预料之内,应对方案也很快的就出来了,既然你不允许同时读取操作一份模板,那我将模板复制成多个,每个Task按需使用闲置模板不就行了,这里也怪自己太自信,没先试下手工复制几份模板先测试下,直接就上手开始了模板按需自动复制的编码,一番测试修改后,最终代码如下:

    /// <summary>
    /// 模板文件管理器
    /// </summary>
    public class TemplateManager
    {
        private readonly IDictionary<int, bool> _dictionary = new Dictionary<int, bool>();//实际自己lock,所以没用ConcurrentDictionary
        private readonly HashSet<int> _hashSet = new HashSet<int>();//因为更新发生在lock之外,所以还需要单独维护Keys
        private string _templatePath;
        private string _copyDirectoryPath;
        private string _fileNameFormat;
        private object _lockObj = new object();
        /// <summary>
        /// 模板文件管理器
        /// </summary>
        /// <param name="templatePath">模板文件路径</param>
        /// <param name="copyDirectoryPath">用于存放复制文件的文件夹</param>
        public TemplateManager(string templatePath, string copyDirectoryPath = "Templates")
        {
            if (!File.Exists(templatePath))
            {
                throw new FileNotFoundException();
            }
            this._templatePath = templatePath;
            this._copyDirectoryPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, copyDirectoryPath);
            if (!Directory.Exists(_copyDirectoryPath))
            {
                Directory.CreateDirectory(_copyDirectoryPath);
            }
            var fileName = Path.GetFileNameWithoutExtension(templatePath);
            var ext = Path.GetExtension(templatePath);
            this._fileNameFormat = $"{fileName}_{{0}}{ext}";
            var files = Directory.GetFiles(_copyDirectoryPath, $"{fileName}_*{ext}", SearchOption.TopDirectoryOnly);
            foreach (var file in files)
            {
                var id = this.GetIdentity(file);
                if (id.HasValue)
                {
                    this._dictionary[id.Value] = false;
                    this._hashSet.Add(id.Value);
                }
            }
        }

        private int? GetIdentity(string path)
        {
            var tmpName = Path.GetFileNameWithoutExtension(path);
            var match = Regex.Match(tmpName, @"\d+$");
            if (match.Success)
            {
                return int.Parse(match.Value);
            }
            return null;
        }
        /// <summary>
        /// 请求使用文件
        /// </summary>
        /// <returns>文件的完整物理路径</returns>
        public string Use()
        {
            int? id = null;
            lock (this._lockObj)
            {
                foreach (var key in this._hashSet)
                {
                    if (!this._dictionary[key])
                    {
                        id = key;
                        break;
                    }
                }
                if (!id.HasValue)
                {
                    id = this._dictionary.Count + 1;
                    this._hashSet.Add(id.Value);
                }
                this._dictionary[id.Value] = true;
            }
            var fileName = string.Format(this._fileNameFormat, id.Value);
            return CheckFileExist(fileName);
        }
        private string CheckFileExist(string fileName)
        {
            var filePath = Path.Combine(this._copyDirectoryPath, fileName);
            if (!File.Exists(filePath))
            {
                File.Copy(this._templatePath, filePath, true);
            }
            return filePath;
        }
        /// <summary>
        /// 文件使用完后进行释放
        /// </summary>
        /// <param name="path">文件的完整路径</param>
        public void Release(string path)
        {
            var id = this.GetIdentity(path);
            if (id.HasValue && this._dictionary.ContainsKey(id.Value))
            {
                this._dictionary[id.Value] = false;
            }
        }
    }
每一个new出来的TemplateManager,都对应一份文件模板,复制出来的模板默认按原文件名+数字编号的命名规则进行命名,使用时先通过Use方法来申请要使用的文件模板路径,当使用完后再通过Release方法来告知程序该模板已使用完毕,可供下次重用。

代码本身是没问题的,问题出在FreeSpire.Pdf上,它居然又报了一个xxx已存在的异常!!!而且该异常一定是在多线程并发操作时才产生,只有一个线程操作时根本不会有任何异常!!!反复尝试都无法解决该问题,后来尝试着试了下商业版,结果只能是What the fuck,商业版一点问题都没!!!后来仔细一看,商业版居然比免费版在pdf加载上还多了个方法PdfDocument.LoadFromBytes(免费版只有PdfDocument.LoadFromFile方法),也正是这个方法,让我立刻意识到这应该是免费版中的一个坑,免费版不支持并发操作!!!

而之所以这样一份失败的代码,还特此写博客记录,一方面是提醒自己,如果一种服务,即同时存在免费版,又存在商业版,那么其必然会有所限制,所以不要轻易相信其纸面声明的区别;另一方面,可能还是期待这份代码或其思路可能在其他地方能发挥它的作用吧!



猜你喜欢

转载自blog.csdn.net/starfd/article/details/80266305
今日推荐