public void Create()
        {
            SatelliteAssemblyFinder?satelliteAssemblyFinder = null;

            try {
                var opts = new ParallelOptions {
                    CancellationToken      = options.CancellationToken,
                    MaxDegreeOfParallelism = options.NumberOfThreads <= 0 ? Environment.ProcessorCount : options.NumberOfThreads,
                };
                var filenameCreator = new FilenameCreator(options.Directory);
                var ctx             = new DecompileContext(options.CancellationToken, logger);
                satelliteAssemblyFinder = new SatelliteAssemblyFinder();
                Parallel.ForEach(options.ProjectModules, opts, modOpts => {
                    options.CancellationToken.ThrowIfCancellationRequested();
                    string name;
                    lock (filenameCreator)
                        name = filenameCreator.Create(modOpts.Module);
                    var p = new Project(modOpts, name, satelliteAssemblyFinder, options.CreateDecompilerOutput);
                    lock (projects)
                        projects.Add(p);
                    p.CreateProjectFiles(ctx);
                });

                var  jobs = GetJobs().ToArray();
                bool writeSolutionFile = !string.IsNullOrEmpty(options.SolutionFilename);
                int  maxProgress       = jobs.Length + projects.Count;
                if (writeSolutionFile)
                {
                    maxProgress++;
                }
                progressListener.SetMaxProgress(maxProgress);

                Parallel.ForEach(GetJobs(), opts, job => {
                    options.CancellationToken.ThrowIfCancellationRequested();
                    try {
                        job.Create(ctx);
                    }
                    catch (OperationCanceledException) {
                        throw;
                    }
                    catch (Exception ex) {
                        if (job is IFileJob fjob)
                        {
                            logger.Error(string.Format(dnSpy_Decompiler_Resources.MSBuild_FileCreationFailed3, fjob.Filename, job.Description, ex.Message));
                        }
                        else
                        {
                            logger.Error(string.Format(dnSpy_Decompiler_Resources.MSBuild_FileCreationFailed2, job.Description, ex.Message));
                        }
                    }
                    progressListener.SetProgress(Interlocked.Increment(ref totalProgress));
                });
                Parallel.ForEach(projects, opts, p => {
                    options.CancellationToken.ThrowIfCancellationRequested();
                    try {
                        var writer = new ProjectWriter(p, p.Options.ProjectVersion ?? options.ProjectVersion, projects, options.UserGACPaths);
                        writer.Write();
                    }
                    catch (OperationCanceledException) {
                        throw;
                    }
                    catch (Exception ex) {
                        logger.Error(string.Format(dnSpy_Decompiler_Resources.MSBuild_FailedToCreateProjectFile, p.Filename, ex.Message));
                    }
                    progressListener.SetProgress(Interlocked.Increment(ref totalProgress));
                });
                if (writeSolutionFile)
                {
                    options.CancellationToken.ThrowIfCancellationRequested();
                    try {
                        var writer = new SolutionWriter(options.ProjectVersion, projects, SolutionFilename);
                        writer.Write();
                    }
                    catch (OperationCanceledException) {
                        throw;
                    }
                    catch (Exception ex) {
                        logger.Error(string.Format(dnSpy_Decompiler_Resources.MSBuild_FailedToCreateSolutionFile, SolutionFilename, ex.Message));
                    }
                    progressListener.SetProgress(Interlocked.Increment(ref totalProgress));
                }
                Debug.Assert(totalProgress == maxProgress);
                progressListener.SetProgress(maxProgress);
            }
            finally {
                if (satelliteAssemblyFinder is not null)
                {
                    satelliteAssemblyFinder.Dispose();
                }
            }
        }
		public void Create() {
			SatelliteAssemblyFinder satelliteAssemblyFinder = null;
			try {
				var opts = new ParallelOptions {
					CancellationToken = options.CancellationToken,
					MaxDegreeOfParallelism = options.NumberOfThreads <= 0 ? Environment.ProcessorCount : options.NumberOfThreads,
				};
				var filenameCreator = new FilenameCreator(options.Directory);
				var ctx = new DecompileContext(options.CancellationToken, logger);
				satelliteAssemblyFinder = new SatelliteAssemblyFinder();
				Parallel.ForEach(options.ProjectModules, opts, modOpts => {
					options.CancellationToken.ThrowIfCancellationRequested();
					string name;
					lock (filenameCreator)
						name = filenameCreator.Create(modOpts.Module);
					var p = new Project(modOpts, name, satelliteAssemblyFinder, options.CreateDecompilerOutput);
					lock (projects)
						projects.Add(p);
					p.CreateProjectFiles(ctx);
				});

				var jobs = GetJobs().ToArray();
				bool writeSolutionFile = !string.IsNullOrEmpty(options.SolutionFilename);
				int maxProgress = jobs.Length + projects.Count;
				if (writeSolutionFile)
					maxProgress++;
				progressListener.SetMaxProgress(maxProgress);

				Parallel.ForEach(GetJobs(), opts, job => {
					options.CancellationToken.ThrowIfCancellationRequested();
					try {
						job.Create(ctx);
					}
					catch (OperationCanceledException) {
						throw;
					}
					catch (Exception ex) {
						var fjob = job as IFileJob;
						if (fjob != null)
							logger.Error(string.Format(dnSpy_Decompiler_Resources.MSBuild_FileCreationFailed3, fjob.Filename, job.Description, ex.Message));
						else
							logger.Error(string.Format(dnSpy_Decompiler_Resources.MSBuild_FileCreationFailed2, job.Description, ex.Message));
					}
					progressListener.SetProgress(Interlocked.Increment(ref totalProgress));
				});
				Parallel.ForEach(projects, opts, p => {
					options.CancellationToken.ThrowIfCancellationRequested();
					try {
						var writer = new ProjectWriter(p, p.Options.ProjectVersion ?? options.ProjectVersion, projects, options.UserGACPaths);
						writer.Write();
					}
					catch (OperationCanceledException) {
						throw;
					}
					catch (Exception ex) {
						logger.Error(string.Format(dnSpy_Decompiler_Resources.MSBuild_FailedToCreateProjectFile, p.Filename, ex.Message));
					}
					progressListener.SetProgress(Interlocked.Increment(ref totalProgress));
				});
				if (writeSolutionFile) {
					options.CancellationToken.ThrowIfCancellationRequested();
					try {
						var writer = new SolutionWriter(options.ProjectVersion, projects, SolutionFilename);
						writer.Write();
					}
					catch (OperationCanceledException) {
						throw;
					}
					catch (Exception ex) {
						logger.Error(string.Format(dnSpy_Decompiler_Resources.MSBuild_FailedToCreateSolutionFile, SolutionFilename, ex.Message));
					}
					progressListener.SetProgress(Interlocked.Increment(ref totalProgress));
				}
				Debug.Assert(totalProgress == maxProgress);
				progressListener.SetProgress(maxProgress);
			}
			finally {
				if (satelliteAssemblyFinder != null)
					satelliteAssemblyFinder.Dispose();
			}
		}