Как правильно уменьшить размер стека для .NET приложения? Возможно есть какие либо директивы или опции в Visual Studio.
1 Answers
Вы можете, например, воспользоваться конструктором Thread с указанием максимального размера стека.
Если вы планируете запускать Task на этом потоке, имеет смысл реализовать TaskScheduler, который перекинет Task в этот поток.
Или можно воспользоваться готовым scheduler'ом, например, WPF.
Пример кода:
При помощи этого метода можно «перебросить» async-метод в поток, на котором бежит данный WPF-диспетчер:
static class AsyncHelper
{
public static DispatcherRedirector RedirectTo(Dispatcher d)
{
return new DispatcherRedirector(d);
}
}
// http://blogs.msdn.com/b/pfxteam/archive/2011/01/13/10115642.aspx
public struct DispatcherRedirector : INotifyCompletion
{
public DispatcherRedirector(Dispatcher dispatcher)
{
this.dispatcher = dispatcher;
}
#region awaiter
public DispatcherRedirector GetAwaiter()
{
// combined awaiter and awaitable
return this;
}
#endregion
#region awaitable
public bool IsCompleted
{
get
{
// true means execute continuation inline
return dispatcher.CheckAccess();
}
}
public void OnCompleted(Action continuation)
{
dispatcher.BeginInvoke(continuation);
}
public void GetResult() { }
#endregion
Dispatcher dispatcher;
}
Теперь вам нужен поток, в котором бежит диспетчер.
public class DispatcherThread : IDisposable
{
public Dispatcher Dispatcher { get; private set; }
public TaskScheduler TaskScheduler { get; private set; }
Thread thread;
public DispatcherThread(int maxStackSize)
{
using (var barrier = new AutoResetEvent(false))
{
thread = new Thread(() =>
{
Dispatcher = Dispatcher.CurrentDispatcher;
barrier.Set();
Dispatcher.Run();
}, maxStackSize);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
barrier.WaitOne();
}
TaskScheduler = Get(() => TaskScheduler.FromCurrentSynchronizationContext());
}
// ---------------------------------------------
// остальные функции вам не нужны для вашей задачи, но могут пригодиться впоследствии
public void Execute(Action a)
{
if (Dispatcher.CheckAccess())
a();
else
Dispatcher.Invoke(a);
}
public void FireAndForget(Action a)
{
Dispatcher.BeginInvoke(a);
}
public T Get<T>(Func<T> getter)
{
if (Dispatcher.CheckAccess())
return getter();
else
{
T t = default(T);
Dispatcher.Invoke((Action)(() => { t = getter(); }));
return t;
}
}
public Task<T> GetAsync<T>(Func<T> getter)
{
return Dispatcher.InvokeAsync(getter).Task;
}
public Task StartNewTask(Action action)
{
return Task.Factory.StartNew(
action: action,
cancellationToken: CancellationToken.None,
creationOptions: TaskCreationOptions.None,
scheduler: TaskScheduler);
}
public Task<T> StartNewTask<T>(Func<T> function)
{
return Task.Factory.StartNew(
function: function,
cancellationToken: CancellationToken.None,
creationOptions: TaskCreationOptions.None,
scheduler: TaskScheduler);
}
public void Dispose()
{
Dispatcher.InvokeShutdown();
if (thread != Thread.CurrentThread)
thread.Join();
}
}
Этим можно пользоваться, например, так:
using (var t = new DispatcherThread(maxStackSize))
{
await AsyncHelper.RedirectTo(t.Dispatcher);
// остаток метода
}
Обновление: совсем забыл, надо же по идее сбежать из умирающего потока! Например, в thread pool.
static class AsyncHelper
{
public static ThreadPoolRedirector RedirectToThreadPool()
{
return new ThreadPoolRedirector();
}
}
public struct ThreadPoolRedirector : INotifyCompletion
{
#region awaiter
public ThreadPoolRedirector GetAwaiter()
{
// combined awaiter and awaitable
return this;
}
#endregion
#region awaitable
public bool IsCompleted
{
get
{
// true means execute continuation inline
return Thread.CurrentThread.IsThreadPoolThread;
}
}
public void OnCompleted(Action continuation)
{
ThreadPool.QueueUserWorkItem(o => continuation());
}
public void GetResult() { }
#endregion
}
и использовать как
using (var t = new DispatcherThread(maxStackSize))
{
await AsyncHelper.RedirectTo(t.Dispatcher);
// остаток метода
await AsyncHelper.RedirectToThreadPool();
}
Хотя может быть это и не нужно, InvokeShutdown не убивает поток немедленно. Но тем не менее.
Более современная версия DispatcherThread — в этом ответе.
- 206,799
-
-
1@z668: Окей, тогда вам нужен собственный
TaskScheduler, который будет выполнятьTaskна указанном вами потоке или потоках. – VladD Oct 14 '15 at 20:22 -
Благодарю, думаю стоит добавить это в ответ. Пошел ковырять исходники LongRunning. – Alexis Oct 14 '15 at 20:31
-
1
-
Искренне благодарен, данным примером кода вы сэкономили мне ночь без сна. – Alexis Oct 14 '15 at 20:52
-
1@z668: Вот и хорошо :) Не стоит благодарности, информация должна быть свободной! – VladD Oct 14 '15 at 20:53
-
@VladD Интересный способ, спасибо! Ваша любовь к таскам безгранична ;D – ApInvent Oct 14 '15 at 21:21
-
@ApInvent: Способ с
await RedirectTo(...)кажется мне очень изящным, я прожужжал им все уши коллегам. :) – VladD Oct 14 '15 at 21:39 -
Только вот увеличить размер стека потока можно только имея full trust-привилегии. Без них лимит - стандартный мегабайт. А вот уменьшить можно всегда. – Qwertiy Oct 14 '15 at 21:47
-
@Qwertiy: Ну, в вопросе идёт речь именно об уменьшении. Кроме того, если уж в проекте есть нативный код... – VladD Oct 14 '15 at 21:48
-
Эм.. Я как-то не так читаю заголовок темы? "Как правильно увеличить размер стека?" – Qwertiy Oct 14 '15 at 21:49
-
-
Ага, уже прочитал. Только, как бы это, эм.. Стек потока - это 1 мегабайт. В x64 каких-либо сильных ограничений на память нет. Так сколько же потоков надо насоздавать, чтобы не влезть в имеющуюся память? Кстати, я не уверен, не потребляет ли поток ещё какие ресурсы помимо памяти под стек, которые сыграют более значительную роль в лимите на число потоков. – Qwertiy Oct 14 '15 at 21:54
-
@Qwertiy: Вроде ничего особенно большого кроме стека у потока нету. Странно, что x64 упирается в проблему, да. – VladD Oct 14 '15 at 21:55
-
-
Не обязательно большого. Возможно, именно лимитированного другими механизмами. – Qwertiy Oct 14 '15 at 22:00
-
@Qwertiy: Ну, в вопросе как раз ссылка на http://blogs.msdn.com/b/oldnewthing/archive/2005/07/29/444912.aspx – VladD Oct 14 '15 at 22:10
-
Ну там про 32-битные и лимит в 2000. Кстати, а зачем в том коде
CloseHandle(h);? Я так понимаю, поток продолжает жить после этого. Т. е. что эта штука вообще должна сделать с потоком? – Qwertiy Oct 14 '15 at 22:15 -
@Qwertiy: Ну,
h— это хэндл, по которому можно дождаться окончания работы потока. Если не закрыть его, то надо хранить где-то и закрыть в конце, иначе будет resource leak. – VladD Oct 14 '15 at 22:21 -
Но ведь поток жив. Или только мне кажется странным, что хендл закрывается, а ресурс, к которому он привязан, остаётся существовать? – Qwertiy Oct 14 '15 at 22:29
-
Но это же просто хэндл, по которому можно ожидать его окончания. Он не представляет собой поток. – VladD Oct 15 '15 at 09:44
Task'ов, каждый из которых стучится в порт. Всё будет летать по идее даже на одном потоке, если вы не будете пользоваться блокирующими функциями. – VladD Oct 14 '15 at 20:24ConnectAsync, вы удивитесь :) Потоки не нужны. – VladD Oct 14 '15 at 20:25