/// <summary> /// Parse the given <paramref name="fileSpec" /> into a <see cref="MSBuildGlob" /> using a given /// <paramref name="globRoot" />. /// </summary> /// <param name="globRoot"> /// The root of the glob. /// The fixed directory part of the glob and the match arguments (<see cref="IsMatch" /> and <see cref="MatchInfo" />) /// will get normalized against this root. /// If empty, the current working directory is used. /// Cannot be null, and cannot contain invalid path arguments. /// </param> /// <param name="fileSpec">The string to parse</param> /// <returns></returns> public static MSBuildGlob Parse(string globRoot, string fileSpec) { ErrorUtilities.VerifyThrowArgumentNull(globRoot, nameof(globRoot)); ErrorUtilities.VerifyThrowArgumentNull(fileSpec, nameof(fileSpec)); ErrorUtilities.VerifyThrowArgumentInvalidPath(globRoot, nameof(globRoot)); if (string.IsNullOrEmpty(globRoot)) { globRoot = Directory.GetCurrentDirectory(); } globRoot = Strings.WeakIntern(FileUtilities.NormalizePath(globRoot).WithTrailingSlash()); var lazyState = new Lazy <GlobState>(() => { FileMatcher.Default.GetFileSpecInfo( fileSpec, out string fixedDirectoryPart, out string wildcardDirectoryPart, out string filenamePart, out bool needsRecursion, out bool isLegalFileSpec, (fixedDirPart, wildcardDirPart, filePart) => { var normalizedFixedPart = NormalizeTheFixedDirectoryPartAgainstTheGlobRoot(fixedDirPart, globRoot); return(normalizedFixedPart, wildcardDirPart, filePart); }); Regex regex = null; if (isLegalFileSpec) { string matchFileExpression = FileMatcher.RegularExpressionFromFileSpec(fixedDirectoryPart, wildcardDirectoryPart, filenamePart); lock (s_regexCache) { s_regexCache.TryGetValue(matchFileExpression, out regex); } if (regex == null) { // compile the regex since it's expected to be used multiple times Regex newRegex = new Regex(matchFileExpression, FileMatcher.DefaultRegexOptions | RegexOptions.Compiled); lock (s_regexCache) { if (!s_regexCache.TryGetValue(matchFileExpression, out regex)) { s_regexCache[matchFileExpression] = newRegex; } } regex ??= newRegex; } } return(new GlobState(globRoot, fileSpec, isLegalFileSpec, fixedDirectoryPart, wildcardDirectoryPart, filenamePart, needsRecursion, regex)); },
public override async Task InvokeAsync(ILoadingContext <TResult> context, PipeDelegate <TResult> next, CancellationToken cancellationToken = default(CancellationToken)) { if (IsInDesignMode) { await next(context, cancellationToken); return; } var source = context.Current; if (source is string || source is Uri) { if (MemoryCache.TryGetValue(source, out var cache)) { context.Result = cache; return; } await next(context, cancellationToken); var result = context.Result; if (result != null) { MemoryCache[source] = result; } return; } await next(context, cancellationToken); }
/// <inheritdoc /> public override async Task InvokeAsync(ILoadingContext <TSource> context, LoadingPipeDelegate <TSource> next, CancellationToken cancellationToken = default) { if (IsInDesignMode) { await next(context, cancellationToken); return; } var cacheKey = context.Current; if (cacheKey is string || cacheKey is Uri) { if (MemoryCache.TryGetValue(cacheKey, out var cacheValue)) { context.Current = cacheValue; context.AttachSource(cacheValue); return; } await next(context, cancellationToken); if (context.Current is TSource finalValue) { MemoryCache[cacheKey] = finalValue; } return; } await next(context, cancellationToken); }
internal static object GetValue(string varName) { lock (Variables) { INodeData tmp; Variables.TryGetValue(varName, out tmp); return(tmp); } }
public void TryGetValue_ReferenceNotFound() { var dictionary = new WeakValueDictionary <string, string>(); string v; bool result = dictionary.TryGetValue("x", out v); Assert.IsFalse(result); Assert.IsNull(v); Assert.IsFalse(dictionary.Contains("x")); }
public void When_item_is_added_Then_it_can_be_retrieved() { var dict = new WeakValueDictionary <int, Item>(); Item item = new Item(); dict[1] = item; bool exists = dict.TryGetValue(1, out item); Assert.That(dict.Count, Is.EqualTo(1)); Assert.That(exists, Is.True); Assert.That(item, Is.EqualTo(item)); }
public void When_null_item_is_added_Then_its_key_exists() { var dict = new WeakValueDictionary <int, Item>(); dict[1] = null; Item item; bool exists = dict.TryGetValue(1, out item); Assert.That(dict.Count, Is.EqualTo(1)); Assert.That(exists, Is.True); Assert.That(item, Is.Null); }
public void TryGetValue_ReferenceFound() { string k1 = "key"; string v1 = "value"; var dictionary = new WeakValueDictionary <string, string>(); dictionary[k1] = v1; // Now look for the same key we inserted string v2; bool result = dictionary.TryGetValue(k1, out v2); Assertion.AssertEquals(true, result); Assertion.AssertEquals(true, Object.ReferenceEquals(v1, v2)); }
public async Task <BitmapSource> DownloadImageAsync(string url) { if (cache.TryGetValue(url, out var bitmapSource)) { return(bitmapSource); } using (var webClient = new WebClient()) { using (var ms = new MemoryStream(await webClient.DownloadDataTaskAsync(url).ConfigureAwait(false))) { var decoder = BitmapDecoder.Create(ms, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); bitmapSource = decoder.Frames[0]; bitmapSource.Freeze(); cache[url] = bitmapSource; return(bitmapSource); } } }
public void TryGetNullValue_ReferenceFound() { string k1 = "key"; string v1 = null; var dictionary = new WeakValueDictionary <string, string>(); dictionary[k1] = v1; // Now look for the same key we inserted string v2; bool result = dictionary.TryGetValue(k1, out v2); Assertion.AssertEquals(true, result); Assertion.AssertEquals(true, Object.ReferenceEquals(v1, v2)); // Should not scavenge values that are null, rather than collected dictionary.Scavenge(); Assertion.AssertEquals(1, dictionary.Count); }
/// <summary> /// Discard any entries (weak and strong) which do not have the explicitlyLoaded flag set. /// </summary> internal void DiscardImplicitReferences() { lock (_locker) { // Make a new Weak cache only with items that have been explicitly loaded, this will be a small number, there will most likely // be many items which were not explicitly loaded (ie p2p references). WeakValueDictionary <string, ProjectRootElement> oldWeakCache = _weakCache; _weakCache = new WeakValueDictionary <string, ProjectRootElement>(StringComparer.OrdinalIgnoreCase); LinkedList <ProjectRootElement> oldStrongCache = _strongCache; _strongCache = new LinkedList <ProjectRootElement>(); foreach (string projectPath in oldWeakCache.Keys) { ProjectRootElement rootElement; if (oldWeakCache.TryGetValue(projectPath, out rootElement)) { if (rootElement.IsExplicitlyLoaded) { _weakCache[projectPath] = rootElement; } if (rootElement.IsExplicitlyLoaded && oldStrongCache.Contains(rootElement)) { _strongCache.AddFirst(rootElement); } else { _strongCache.Remove(rootElement); RaiseProjectRootElementRemovedFromStrongCache(rootElement); } } } } }
internal override ProjectRootElement Get(string projectFile, OpenProjectRootElement loadProjectRootElement, bool isExplicitlyLoaded, bool?preserveFormatting) { #if DEBUG // Verify that loadProjectRootElement delegate does not call ProjectRootElementCache.Get(). s_getEntriesNumber++; ErrorUtilities.VerifyThrow( s_getEntriesNumber == 1, "Reentrance to the ProjectRootElementCache.Get function detected." ); try { #endif // Should already have been canonicalized ErrorUtilities.VerifyThrowInternalRooted(projectFile); ProjectRootElement projectRootElement; lock (_locker) { _weakCache.TryGetValue(projectFile, out projectRootElement); if (projectRootElement != null) { BoostEntryInStrongCache(projectRootElement); // An implicit load will never reset the explicit flag. if (isExplicitlyLoaded) { projectRootElement.MarkAsExplicitlyLoaded(); } } else { DebugTraceCache("Not found in cache: ", projectFile); } if (preserveFormatting != null && projectRootElement != null && projectRootElement.XmlDocument.PreserveWhitespace != preserveFormatting) { // Cached project doesn't match preserveFormatting setting, so reload it projectRootElement.Reload(true, preserveFormatting); } } bool projectRootElementIsInvalid = IsInvalidEntry(projectFile, projectRootElement); if (projectRootElementIsInvalid) { DebugTraceCache("Not satisfied from cache: ", projectFile); ForgetEntryIfExists(projectRootElement); } if (loadProjectRootElement == null) { if (projectRootElement == null || projectRootElementIsInvalid) { return(null); } else { DebugTraceCache("Satisfied from XML cache: ", projectFile); return(projectRootElement); } } // Use openProjectRootElement to reload the element if the cache element does not exist or need to be reloaded. if (projectRootElement == null || projectRootElementIsInvalid) { // We do not lock loading with common _locker of the cache, to avoid lock contention. // Decided also not to lock this section with the key specific locker to avoid the overhead and code overcomplication, as // it is not likely that two threads would use Get function for the same project simultaneously and it is not a big deal if in some cases we load the same project twice. projectRootElement = loadProjectRootElement(projectFile, this); ErrorUtilities.VerifyThrowInternalNull(projectRootElement, "projectRootElement"); ErrorUtilities.VerifyThrow( projectRootElement.FullPath.Equals(projectFile, StringComparison.OrdinalIgnoreCase), "Got project back with incorrect path. Expected path: {0}, received path: {1}.", projectFile, projectRootElement.FullPath ); // An implicit load will never reset the explicit flag. if (isExplicitlyLoaded) { projectRootElement.MarkAsExplicitlyLoaded(); } // Update cache element. // It is unlikely, but it might be that while without the lock, the projectRootElement in cache was updated by another thread. // And here its entry will be replaced with the loaded projectRootElement. This is fine: // if loaded projectRootElement is out of date (so, it changed since the time we loaded it), it will be updated the next time some thread calls Get function. AddEntry(projectRootElement); } else { DebugTraceCache("Satisfied from XML cache: ", projectFile); } return(projectRootElement); #if DEBUG } finally { s_getEntriesNumber--; } #endif }
public void TryGetNullValue_ReferenceFound() { string k1 = "key"; string v1 = null; var dictionary = new WeakValueDictionary<string, string>(); dictionary[k1] = v1; // Now look for the same key we inserted string v2; bool result = dictionary.TryGetValue(k1, out v2); Assertion.AssertEquals(true, result); Assertion.AssertEquals(true, Object.ReferenceEquals(v1, v2)); // Should not scavenge values that are null, rather than collected dictionary.Scavenge(); Assertion.AssertEquals(1, dictionary.Count); }
public void TryGetValue_ReferenceNotFound() { var dictionary = new WeakValueDictionary<string, string>(); string v; bool result = dictionary.TryGetValue("x", out v); Assert.IsFalse(result); Assert.IsNull(v); Assert.IsFalse(dictionary.Contains("x")); }
public void TryGetValue_ReferenceFound() { string k1 = "key"; string v1 = "value"; var dictionary = new WeakValueDictionary<string, string>(); dictionary[k1] = v1; // Now look for the same key we inserted string v2; bool result = dictionary.TryGetValue(k1, out v2); Assertion.AssertEquals(true, result); Assertion.AssertEquals(true, Object.ReferenceEquals(v1, v2)); }
public async Task <BitmapResult> GetBitmapAsync(string source) { if (source == null) { throw new ArgumentNullException(nameof(source)); } // 检查内存缓存。 BitmapImage bitmap; if (CacheBitmapImages.TryGetValue(source, out bitmap)) { // 内存缓存存在,直接使用内存缓存。 return(new BitmapResult(bitmap)); } else { var uriSource = ToUriSource(source); if (IsHttpUri(uriSource)) { var cacheFilePath = GetCacheFilePath(uriSource); if (File.Exists(cacheFilePath)) { var bytes = await FileExtensions.ReadAllBytesAsync(cacheFilePath); bitmap = new BitmapImage(); try { await bitmap.SetSourceAsync(new MemoryStream(bytes).AsRandomAccessStream()); // 放入内存缓存。 CacheBitmapImages[source] = bitmap; return(new BitmapResult(bitmap)); } catch (Exception ex) { // 缓存文件无法加载,删除缓存文件。 async void AsyncAction() { await Task.Run(() => { try { File.Delete(cacheFilePath); } catch (Exception) { // ignored } }); } AsyncAction(); return(new BitmapResult(ex)); } } else { Task <byte[]> imageDownloadTask; if (!ImageDownloadTasks.TryGetValue(source, out imageDownloadTask)) { imageDownloadTask = DownloadImageAsync(uriSource); ImageDownloadTasks[source] = imageDownloadTask; } byte[] bytes; try { bytes = await imageDownloadTask; } catch (TaskCanceledException ex) { ImageDownloadTasks.TryRemove(source, out imageDownloadTask); return(new BitmapResult(ex)); } catch (HttpRequestException ex) { ImageDownloadTasks.TryRemove(source, out imageDownloadTask); return(new BitmapResult(ex)); } bitmap = new BitmapImage(); try { await bitmap.SetSourceAsync(new MemoryStream(bytes).AsRandomAccessStream()); // 放入内存缓存。 CacheBitmapImages[source] = bitmap; async void AsyncAction() { try { Directory.CreateDirectory(CacheFolderPath); await FileExtensions.WriteAllBytesAsync(cacheFilePath, bytes); } catch (Exception) { // ignored } finally { ImageDownloadTasks.TryRemove(source, out imageDownloadTask); } } AsyncAction(); return(new BitmapResult(bitmap)); } catch (Exception ex) { ImageDownloadTasks.TryRemove(source, out imageDownloadTask); return(new BitmapResult(ex)); } } } else { byte[] bytes; try { if (string.Equals(uriSource.Scheme, FileScheme, StringComparison.OrdinalIgnoreCase)) { // 绝对路径。 bytes = await FileExtensions.ReadAllBytesAsync(source); } else { // ms-appx 或 ms-appdata,其它路径不支持。 var file = await StorageFile.GetFileFromApplicationUriAsync(uriSource); var buffer = await FileIO.ReadBufferAsync(file); bytes = buffer.ToArray(); } } catch (Exception ex) { return(new BitmapResult(ex)); } bitmap = new BitmapImage(); try { await bitmap.SetSourceAsync(new MemoryStream(bytes).AsRandomAccessStream()); // 放入内存缓存。 CacheBitmapImages[source] = bitmap; return(new BitmapResult(bitmap)); } catch (Exception ex) { return(new BitmapResult(ex)); } } } }
/// <summary> /// Returns an existing ProjectRootElement for the specified file path, if any. /// If none exists, calls the provided delegate to load one, and adds that to the cache. /// The reason that it calls back to do this is so that the cache is locked between determining /// that the entry does not exist and adding the entry. /// /// If <see cref="_autoReloadFromDisk"/> was set to true, and the file on disk has changed since it was cached, /// it will be reloaded before being returned. /// /// Thread safe. /// </summary> /// <remarks> /// Never needs to consult the strong cache as well, since if the item is in there, it will /// not have left the weak cache. /// If item is found, boosts it to the top of the strong cache. /// </remarks> /// <param name="projectFile">The project file which contains the ProjectRootElement. Must be a full path.</param> /// <param name="openProjectRootElement">The delegate to use to load if necessary. May be null.</param> /// <param name="isExplicitlyLoaded"><code>true</code> if the project is explicitly loaded, otherwise <code>false</code>.</param> /// <param name="preserveFormatting"><code>true</code> to the project was loaded with the formated preserved, otherwise <code>false</code>.</param> /// <returns>The ProjectRootElement instance if one exists. Null otherwise.</returns> internal override ProjectRootElement Get(string projectFile, OpenProjectRootElement openProjectRootElement, bool isExplicitlyLoaded, bool?preserveFormatting) { // Should already have been canonicalized ErrorUtilities.VerifyThrowInternalRooted(projectFile); lock (_locker) { ProjectRootElement projectRootElement; _weakCache.TryGetValue(projectFile, out projectRootElement); if (preserveFormatting != null && projectRootElement != null && projectRootElement.XmlDocument.PreserveWhitespace != preserveFormatting) { // Cached project doesn't match preserveFormatting setting, so reload it projectRootElement.Reload(true, preserveFormatting); } if (projectRootElement != null && _autoReloadFromDisk) { FileInfo fileInfo = FileUtilities.GetFileInfoNoThrow(projectFile); // If the file doesn't exist on disk, go ahead and use the cached version. // It's an in-memory project that hasn't been saved yet. if (fileInfo != null) { bool forgetEntry = false; if (fileInfo.LastWriteTime != projectRootElement.LastWriteTimeWhenRead) { // File was changed on disk by external means. Cached version is no longer reliable. // We could throw here or ignore the problem, but it is a common and reasonable pattern to change a file // externally and load a new project over it to see the new content. So we dump it from the cache // to force a load from disk. There might then exist more than one ProjectRootElement with the same path, // but clients ought not get themselves into such a state - and unless they save them to disk, // it may not be a problem. forgetEntry = true; } else if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDCACHECHECKFILECONTENT"))) { // QA tests run too fast for the timestamp check to work. This environment variable is for their // use: it checks the file content as well as the timestamp. That's better than completely disabling // the cache as we get test coverage of the rest of the cache code. XmlDocument document = new XmlDocument(); document.PreserveWhitespace = projectRootElement.XmlDocument.PreserveWhitespace; using (var xtr = XmlReaderExtension.Create(projectRootElement.FullPath, projectRootElement.ProjectRootElementCache.LoadProjectsReadOnly)) { document.Load(xtr.Reader); } string diskContent = document.OuterXml; string cacheContent = projectRootElement.XmlDocument.OuterXml; if (diskContent != cacheContent) { forgetEntry = true; } } if (forgetEntry) { ForgetEntry(projectRootElement); DebugTraceCache("Out of date dropped from XML cache: ", projectFile); projectRootElement = null; } } } if (projectRootElement == null && openProjectRootElement != null) { projectRootElement = openProjectRootElement(projectFile, this); ErrorUtilities.VerifyThrowInternalNull(projectRootElement, "projectRootElement"); ErrorUtilities.VerifyThrow(projectRootElement.FullPath == projectFile, "Got project back with incorrect path"); ErrorUtilities.VerifyThrow(_weakCache.Contains(projectFile), "Open should have renamed into cache and boosted"); } else if (projectRootElement != null) { DebugTraceCache("Satisfied from XML cache: ", projectFile); BoostEntryInStrongCache(projectRootElement); } // An implicit load will never reset the explicit flag. if (projectRootElement != null && isExplicitlyLoaded) { projectRootElement.MarkAsExplicitlyLoaded(); } return(projectRootElement); } }
/// <summary> /// Parse the given <paramref name="fileSpec" /> into a <see cref="MSBuildGlob" /> using a given /// <paramref name="globRoot" />. /// </summary> /// <param name="globRoot"> /// The root of the glob. /// The fixed directory part of the glob and the match arguments (<see cref="IsMatch" /> and <see cref="MatchInfo" />) /// will get normalized against this root. /// If empty, the current working directory is used. /// Cannot be null, and cannot contain invalid path arguments. /// </param> /// <param name="fileSpec">The string to parse</param> /// <returns></returns> public static MSBuildGlob Parse(string globRoot, string fileSpec) { ErrorUtilities.VerifyThrowArgumentNull(globRoot, nameof(globRoot)); ErrorUtilities.VerifyThrowArgumentNull(fileSpec, nameof(fileSpec)); ErrorUtilities.VerifyThrowArgumentInvalidPath(globRoot, nameof(globRoot)); if (string.IsNullOrEmpty(globRoot)) { globRoot = Directory.GetCurrentDirectory(); } globRoot = Strings.WeakIntern(FileUtilities.NormalizePath(globRoot).WithTrailingSlash()); var lazyState = new Lazy <GlobState>(() => { FileMatcher.Default.GetFileSpecInfo( fileSpec, out string fixedDirectoryPart, out string wildcardDirectoryPart, out string filenamePart, out bool needsRecursion, out bool isLegalFileSpec, (fixedDirPart, wildcardDirPart, filePart) => { var normalizedFixedPart = NormalizeTheFixedDirectoryPartAgainstTheGlobRoot(fixedDirPart, globRoot); return(normalizedFixedPart, wildcardDirPart, filePart); }); Regex regex = null; if (isLegalFileSpec) { string matchFileExpression = FileMatcher.RegularExpressionFromFileSpec(fixedDirectoryPart, wildcardDirectoryPart, filenamePart); lock (s_regexCache) { s_regexCache.TryGetValue(matchFileExpression, out regex); } if (regex == null) { RegexOptions regexOptions = FileMatcher.DefaultRegexOptions; // compile the regex since it's expected to be used multiple times // For the kind of regexes used here, compilation on .NET Framework tends to be expensive and not worth the small // run-time boost so it's enabled only on .NET Core by default. #if RUNTIME_TYPE_NETCORE bool compileRegex = true; #else bool compileRegex = !ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_0); #endif if (compileRegex) { regexOptions |= RegexOptions.Compiled; } Regex newRegex = new Regex(matchFileExpression, regexOptions); lock (s_regexCache) { if (!s_regexCache.TryGetValue(matchFileExpression, out regex)) { s_regexCache[matchFileExpression] = newRegex; } } regex ??= newRegex; } } return(new GlobState(globRoot, fileSpec, isLegalFileSpec, fixedDirectoryPart, wildcardDirectoryPart, filenamePart, needsRecursion, regex)); },
public async Task <BitmapResult> GetBitmapAsync(string source) { if (source == null) { throw new ArgumentNullException(nameof(source)); } // 检查内存缓存。 BitmapImage bitmap; if (CacheBitmapImages.TryGetValue(source, out bitmap)) { // 内存缓存存在,直接使用内存缓存。 return(new BitmapResult(bitmap)); } else { var uriSource = ToUriSource(source); if (IsHttpUri(uriSource)) { var cacheFilePath = GetCacheFilePath(uriSource); if (File.Exists(cacheFilePath)) { await SemaphoreSlim.WaitAsync(); try { bitmap = new BitmapImage(); bitmap.BeginInit(); bitmap.UriSource = new Uri(cacheFilePath); bitmap.EndInit(); // 放入内存缓存。 CacheBitmapImages[source] = bitmap; return(new BitmapResult(bitmap)); } catch (NotSupportedException ex) { // 缓存文件无法加载,删除缓存文件。 async void AsyncAction() { await Task.Run(() => { try { File.Delete(cacheFilePath); } catch (Exception) { // ignored } }); } AsyncAction(); return(new BitmapResult(ex)); } finally { await Task.Delay(1);// 防止同时加载大量图片时,UI 线程卡死。 SemaphoreSlim.Release(); } } else { Task <byte[]> imageDownloadTask; if (!ImageDownloadTasks.TryGetValue(source, out imageDownloadTask)) { imageDownloadTask = DownloadImageAsync(uriSource); ImageDownloadTasks[source] = imageDownloadTask; } byte[] bytes; try { bytes = await imageDownloadTask; } catch (HttpRequestException ex) { ImageDownloadTasks.TryRemove(source, out imageDownloadTask); return(new BitmapResult(ex)); } await SemaphoreSlim.WaitAsync(); try { bitmap = new BitmapImage(); bitmap.BeginInit(); bitmap.StreamSource = new MemoryStream(bytes); bitmap.EndInit(); // 放入内存缓存。 CacheBitmapImages[source] = bitmap; Action asyncAction = async() => { try { Directory.CreateDirectory(CacheFolderPath); await FileExtensions.WriteAllBytesAsync(cacheFilePath, bytes); } catch (Exception) { // ignored } finally { ImageDownloadTasks.TryRemove(source, out imageDownloadTask); } }; asyncAction.Invoke(); return(new BitmapResult(bitmap)); } catch (NotSupportedException ex) { ImageDownloadTasks.TryRemove(source, out imageDownloadTask); return(new BitmapResult(ex)); } finally { await Task.Delay(1);// 防止同时加载大量图片时,UI 线程卡死。 SemaphoreSlim.Release(); } } } else { await SemaphoreSlim.WaitAsync(); try { bitmap = new BitmapImage(); bitmap.BeginInit(); bitmap.UriSource = uriSource; bitmap.EndInit(); // 放入内存缓存。 CacheBitmapImages[source] = bitmap; return(new BitmapResult(bitmap)); } catch (FileNotFoundException ex) { return(new BitmapResult(ex)); } catch (IOException ex) { return(new BitmapResult(ex)); } catch (NotSupportedException ex) { return(new BitmapResult(ex)); } finally { await Task.Delay(1);// 防止同时加载大量图片时,UI 线程卡死。 SemaphoreSlim.Release(); } } } }