Загрузка ресурса в фоновом потоке в не асинхронном c контексте - PullRequest
1 голос
/ 16 марта 2020

Я работаю с внешней библиотекой, которая ожидает от меня создания растровых изображений, когда она вызывает GetImage для следующего интерфейса, который она предоставляет:

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

Библиотека запрашивает их навалом - т.е. она вызывает GetImage() неоднократно в потоке пользовательского интерфейса, создавая существенную задержку пользовательского интерфейса. Теперь у меня есть время для предварительного рендеринга изображений для каждого из этих идентификаторов, прежде чем библиотека фактически запросит их. Я хотел бы сделать это в фоновом потоке, но я явно не в состоянии вернуть Task<Bitmap> обратно через интерфейс.

То, что я по сути пытаюсь достичь, резюмировано ниже: Я создаю библиотеку - MySvgLibrary:

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

Теперь я хочу Task.Run(() => _myLibrary.Preload() }. Учитывая, что я не думаю, что могу использовать async / await здесь (так как я не могу вернуть Task<Bitmap>, я не вижу, как я могу использовать, скажем, TaskCompletionSource в этом контексте. Откуда я знаю, что Preload закончен? Я имею в виду, я мог бы проверить, если Library равно null, и вращаться, пока не получится (и это работает, кстати), но такой подход вызывает у меня тошноту. Предложения?

1 Ответ

1 голос
/ 16 марта 2020

Вот реализация класса MySvgLibrary. Он использует ConcurrentDictionary для хранения растровых изображений и 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.

...