Использование объекта в продолжении, гарантируя, что он не будет удален до того, как он вам понадобится - PullRequest
3 голосов
/ 21 июня 2020

В приложении Xamarin. iOS по разным причинам мы не можем использовать async / await. Поэтому мы используем Task.ContinueWith. Проблема в том, что объекты, на которые мы полагаемся в продолжении, удаляются до вызова продолжения.

using (UIImage image = // Somehow get the image)
{
    DoSomethingWith(image);
}

void DoSomethingWithImage(UIImage image)
{
    UploadImageAsync(image).ContinueWith(t =>
    {
        // Image has been disposed by this point.
        DisplayResult(t.Result, image);
    });
}

Мне интересно получить идеи о том, как управлять этим случаем. Варианты, которые я рассмотрел:

  • Удалите блок using и оставьте image go вне области видимости. Это было бы плохой идеей, поскольку UIImage может быть довольно большим.
  • Удалите using и вызовите Dispose() в продолжении. Это кажется подверженным ошибкам, поскольку, где бы ни существовал подобный код, вы удаляете объект далеко от того места, где он был создан.
  • Оберните вызов DoSomethingWithImage() в Task.Run(() => DoSomethingWithImage(image)).ContinueWith(t => image.Dispose()). Итак, у вас есть продолжение в продолжении. Это кажется слишком сложным для данного сценария.
  • Какие еще варианты я упускаю?

1 Ответ

0 голосов
/ 23 июня 2020

Прежде всего, это хороший вопрос. Основываясь на упомянутых вариантах, я думаю, вам нужно сменить ангела.

В вашей текущей реализации область действия UIImage тесно связана с методом DoSomethingWith. Другими словами, полученное изображение используется только внутри этого метода. Я вижу два разных пути, как это может развиваться:

  1. Ввести DoSomethingElseWith метод
  2. Избавиться от DoSomethingWith и «встроенных» UploadImage и DisplayResult

В любом случае в вашем блоке using будет более одного метода. Это будет означать, что очистка UIImage не входит в обязанности методов. В обычных случаях вы бы сделали это, await добавив последний и позволив блоку using делать свою работу, верно? Если вы не можете дождаться этого, то наиболее очевидным выбором для продолжения является использование ContinueWith. (Другие: TaskFactory.ContinueWhenAll, TaskFactory.ContinueWhenAny, Task.WhenAll, Task.WhenAny, для дополнительная информация ).

UIImage image = RetrieveImage();
var uploadJob = UploadImage(image);
var displayJob = uploadJob.ContinueWith((t => DisplayResult(t.Result, image)).Unwrap();
var disposeJob = displayJob.ContinueWith(_ => image.Dispose());

В качестве альтернативы вы можете использовать какой-то короткий механизм сигнализации (син. c примитив), чтобы указать, когда была завершена последняя асинхронная c операция, например AutoResetEvent, CountdownEvent, TaskComplitionSource.

Третий вариант может заключаться в использовании TPL Dataflow . С этим вы можете соединить вместе маленькие части и использовать Завершение , чтобы инициировать удаление в качестве последнего действия.

...