在非异步上下文中在后台线程上加载资源

Loading resource on background thread in non-async context

我正在使用一个外部库,它希望我在调用 GetImage 时为它公开的以下接口生成位图:

public interface IImageProvider
{
    Bitmap GetImage(string imageId);
}

库批量请求它们 - 即它在 UI 线程上重复调用 GetImage(),从而造成大量 UI 延迟。现在,我有时间在图书馆实际要求它们之前为这些 ID 中的每一个预渲染图像。我想在后台线程上这样做,但我显然无法通过界面返回 return Task<Bitmap>

我基本上想要实现的目标总结如下:假设我创建了一个库 - MySvgLibrary:

public interface MySvgLibrary
{
    void Preload();
    Dictionary<string, Bitmap> Library { get; }
}

我现在想Task.Run(() => _myLibrary.Preload() }。考虑到我不认为我可以在这里使用 async/await(因为我不能 return a Task<Bitmap>,我不知道如何使用 TaskCompletionSource在这种情况下。我怎么知道 Preload 完成了?我的意思是,我可以检查 Library 是否为 null 并旋转直到它不是(这确实有效,顺便说一句)但这种方法让我感到恶心。有什么建议吗?

这里是 MySvgLibrary class 的一个实现。它使用 ConcurrentDictionary for storing the bitmaps, and a SemaphoreSlim 来控制并行度(允许多少个线程并行创建图像)。

public class MySvgLibrary
{
    private readonly ConcurrentDictionary<string, Task<Bitmap>> _dictionary;
    private readonly SemaphoreSlim _semaphore;

    public MySvgLibrary(int degreeOfParallelism = 1)
    {
        _dictionary = new ConcurrentDictionary<string, Task<Bitmap>>();
        _semaphore = new SemaphoreSlim(degreeOfParallelism);
    }

    public Task<Bitmap> GetImageAsync(string key)
    {
        return _dictionary.GetOrAdd(key, _ => Task.Run(async () =>
        {
            await _semaphore.WaitAsync().ConfigureAwait(false);
            try
            {
                return CreateImage(key);
            }
            finally
            {
                _semaphore.Release();
            }
        }));
    }

    public Bitmap GetImage(string key)
    {
        return GetImageAsync(key).GetAwaiter().GetResult();
    }

    public void PreloadImage(string key)
    {
        var fireAndForget = GetImageAsync(key);
    }

    private Bitmap CreateImage(string key)
    {
        Thread.Sleep(1000); // Simulate some heavy computation
        return new Bitmap(1, 1);
    }
}

用法示例:

var svgLibrary = new MySvgLibrary(degreeOfParallelism: 2);
svgLibrary.PreloadImage("SomeKey"); // the preloading happens in background threads
Bitmap bitmap = svgLibrary.GetImage("SomeKey"); // blocks if the bitmap is not ready yet

您应该将生成图像的实际代码放入 CreateImage 方法中。如果 CreateImage 抛出异常,将在调用 GetImage 时传播并重新抛出异常。