Вопрос: Как правильно организовать многопоточность asp.net?

Лучший ответ:
Для ресайзинга картинок вы можете использовать готовое решение – imageresizing.net. Оно выполнено в виде HTTP Module для ASP.NET и производит ресайзинг картинок "на лету". То есть во время загрузки картинки производится только ее загрузка, без трансформаций. Ресайзинг же производится тогда, когда запрашивается специфичный размер самой картинки и выполняется асинхронно, поскольку страница уже отображается у пользователя. Также используется кэш, чтобы избежать постоянной регенерации картинок.
Но так как вопрос был про "многопоточность asp.net", то поделюсь соображениями еще и по этой теме. Сразу скажу, что есть готовое решение – HangFire, но также интересны и причины его появления.
В ASP.NET каждый запрос выполняется внутри отдельного потока. Так как создание потока является достаточно "дорогой" операцией, то для каждого запроса отдается "в аренду" готовый поток из пула потоков.
В интернете ходит множество слухов, что долгоиграющие запросы ASP.NET могут "забить" все потоки из пула, и новые запросы будут нещадно попадать в request queue и ожидать своей очереди на исполнение. Такая ситуация называется Thread pool starvation (или thread pool contention) и приводит к снижению пропускной способности сервера при увеличении количества запросов.
Однако начиная с IIS 7 количество доступных потоков в ASP.NET приложении достаточно большое. Но так как ресайзинг картинки является cpu intensive задачей, то снижение пропускной способности все-таки может произойти из-за нехватки ресурсов процессора, которая усугубляется дорогими и постоянными переключениями между потоками.  
В ASP.NET в общем и в ASP.NET MVC в частности есть понятие асинхронной обработки запросов. В MVC 3 был AsyncController, начиная с MVC 4 и C# 5.0 была введена поддержка async-методов. Однако в этой программной модели запрос должен ждать завершения всех асинхронных операций, и в вашем случае получится, что вы отдаете ресайз картинки на выполнение в другом потоке, и ожидаете его завершение в потоке, который исполняет запрос. В этом случае вы ничего не выигрываете, и даже проигрываете, потому что вместо одного потока на обработку запроса начинает выделяться два потока.
В ASP.NET приложении вы также можете либо создать поток самостоятельно (что плохо, потому что это дорогая операция), либо использовать некоторый пул предварительно инициированных потоков самостоятельно, либо использовать тот же самый Thread Pool (посредством Task-based API либо ThreadPool.QueueUserWorkItem) и не ожидать его завершения, при этом ресайз картинки будет происходить вне пределов контекста обработки запроса. В этом случае пользователю не нужно будет ожидать завершения долгой операции, но здесь может возникнуть следующая проблема.
Если в качестве веб-сервера выступает IIS (что скорее всего так, пока Owin не пошел в массы), то ваше приложение хостится в одном или нескольких рабочих процессах IIS, которые работают под управлением пула приложения. А пул приложения имеет множество всевозможных настроек, которые могут привести к recycling'у приложения. Это хороший процесс, который экономит ресурсы сервера, особенно на shared хостингах. Вот только у него есть таймаут на его завершение, который по-умолчанию равен 90 секундам. 
Когда application pool начинает процесс ресайклинга, он отсылает ASP.NET приложению команду на остановку (shutdown). При этом само ASP.NET приложение перестает принимать новые запросы, и ждет завершения текущих с собственным дефолтным таймаутом (ShutdownTimeout в конфиге application pool) в 30 секунд. Если текущие запросы успевают выполниться за отведенное время, то сразу после этого происходит выгрузка домена приложения.
Вот тут и кроется первая проблема – ASP.NET по-умолчанию просто не обращает внимания на все ваши фоновые потоки, как созданные собственноручно, так и потоки из Thread Pool. И если после завершения обработки текущих запросов ваша фоновая задача не была завершена – это ваши проблемы, она будет просто прервана исключением ThreadAbortException. И если не были предприняты конкретные шаги по ее перезапуску, то сами потом будете объяснять пользователям, почему иногда картинки или отчеты до них так и не доходят. Конечно же, вероятность вроде бы небольшая, но запросов много, причин у recycling'а много, да и время жизни проекта тоже большое. А бороться с последствиями довольно неприятно.
Еще в ASP.NET есть интерфейс IRegisteredObject и методы HostEnvironment.RegisterObject и HosterEnvironment.UnregisterObject (а вот и статья по ним), которые уведомляют ASP.NET о том, что  есть операция, завершения которой нужно ожидать при получения события об остановке приложения. Правда вот ShutdownTimeout при этом никуда не девается, а гарантировать, что все фоновые задачи будут завершены за 30 секунд очень сложно.
Так что основная проблема – это вероятность прерывания потока, выполняющего задачу из-за таймаутов. Я лично ловил несколько раз такие исключения, и разрешение последствий иногда было довольно неприятной штукой. Увеличивать же таймауты не совсем разумно, поскольку именно они помогают приложению сохранять хоть какую-то стабильность, когда возникают гораздо большие проблемы по совершенно другим причинам.
В этой ситуации 100% работающее решение – написать собственное приложение, которое будет "крутиться" постоянно и оформить его в виде службы Windows. Она будет перезапускаться гораздо реже, и таймауты можно настроить так, как душе угодно. Однако в добавок нужно еще организовать протокол общения (тут можно взять, конечно WCF), и разработать грамотную модель обработки задач. Но и первоначальная разработка, и последующее тестирование несколько сложно. А оправдать такое монстроидальное решение тем, что "нам нужна гарантия при ресайзе картинок" бывает тоже довольно трудно.
Самым обидным для меня было то, что для Ruby on Rails было полно всяких решения, которые решали подобную проблему с long-running процессами на стороне веб-сервера на общий манер: Resque, Sidekiq, delayed_job и иже с ними. Для платформы .NET есть несколько разработок на NuGet, но они пока еще слишком слабые, и иногда не дают гарантий того, что задача будет выполнена всегда после ее создания.
Подождав год-другой, я решил все-таки сделать подобный общий инструмент для .NET – HangFire. На данный момент у него версия 0.7, что означает его неполную готовность для массового использования, однако он гораздо стабильнее и функциональнее, чем большинство собственных разработок. Плюс, он сейчас находится в активной разработке. Схема и публичное API довольно стабильно, и оно уже используется в нашей системе, которая находится в продакшене. Так что можете попробовать, а если возникнут вопросы – то смело создавайте тикет на гитхабе.

Худший ответ:
На сервере имеется пул потоков, при получении запроса от клиента, пул выделяет поток в котором и будет выполняться ваша трансформация. Другой запрос запустит на выполнение новый поток и так далее.. т.е. как писали выше, "каждый запрос отрабатывается в отдельном потоке".