Как правильно уменьшить размер стека?

Alexis

Как правильно уменьшить размер стека для .NET приложения? Возможно есть какие либо директивы или опции в Visual Studio.

1 ответ

Alexis

Вы можете, например, воспользоваться конструктором 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();
    }
}
</t></t></t></t></t></t></t></t>

Этим можно пользоваться, например, так:

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 — в этом ответе.

licensed under cc by-sa 3.0 with attribution.