Асинхронное выполнение длительной операции в другом AppDomain
От: Shefango  
Дата: 06.06.13 08:57
Оценка:
Доброго времени суток.
Столкнулся с такой проблемой. Мне нужно провести обмен данными с одной системой, предоставляющей только COM-интерфейс. Делаю я это так:

private void startButton_Click(object sender, EventArgs e)
{
    string assembly = Environment.CurrentDirectory + "\\Import.dll";

    if (File.Exists(assembly))
    {
        AppDomain worker = AppDomain.CreateDomain("Worker");

        Thread t = new Thread(() =>
        {
            Trace.WriteLine(AppDomain.CurrentDomain.FriendlyName);
            AbstractImport import = (AbstractImport)worker.CreateInstanceFromAndUnwrap(
                assembly,
                "Import.Import1CtoTS");

            if (import != null)
            {
                import.Initialize(this);
                import.ProgressChanged += new Shared.ProgressChangedEventHandler(import_ProgressChanged);
                import.Start("ts", "Superuser", "123");
            }

            AppDomain.Unload(worker);
        });

        t.SetApartmentState(ApartmentState.STA);
        t.Start();
    }
}

При первом нажатии на кнопку startButton код отрабатывает нормально. При повторном нажатии обмен тоже отрабатывает, но при закрытии приложения вываливается AppCrash c исключением 0x0eedfade.
Если этот код вызывать синхронно, так:
private void startButton_Click(object sender, EventArgs e)
{
    string assembly = Environment.CurrentDirectory + "\\Import.dll";

    if (File.Exists(assembly))
    {
        AppDomain worker = AppDomain.CreateDomain("Worker");

        //Thread t = new Thread(() =>
        //{
            Trace.WriteLine(AppDomain.CurrentDomain.FriendlyName);
            AbstractImport import = (AbstractImport)worker.CreateInstanceFromAndUnwrap(
                assembly,
                "Import.Import1CtoTS");

            if (import != null)
            {
                import.Initialize(this);
                import.ProgressChanged += new Shared.ProgressChangedEventHandler(import_ProgressChanged);
                import.Start("ts", "Superuser", "123");
            }

            AppDomain.Unload(worker);
        //});

        //t.SetApartmentState(ApartmentState.STA);
        //t.Start();
    }
}

то импорт, выполняемый многократно без перезапуска приложения отрабатывает нормально и приложение не крэшится при выходе.
Помогите решить проблему с выполнением кода в отдельном потоке.
многопоточность appdomain
Re: Асинхронное выполнение длительной операции в другом AppDomain
От: Sharov Россия  
Дата: 06.06.13 10:15
Оценка:
Здравствуйте, Shefango, Вы писали:

А попробуйте делегировать создание потока самому домену,
т.е. есть некий worker, вы вызываете у него метод, и в этом методе
создаете поток. Потом выгружаете домен. Если не ошибаюсь, можно
синхронизироваться через какой-нибудь соотв. статический примитив.
Кодом людям нужно помогать!
Re[2]: Асинхронное выполнение длительной операции в другом AppDomain
От: Shefango  
Дата: 06.06.13 10:58
Оценка:
Здравствуйте, Sharov, Вы писали:

S>А попробуйте делегировать создание потока самому домену,

S>т.е. есть некий worker, вы вызываете у него метод, и в этом методе
S>создаете поток. Потом выгружаете домен. Если не ошибаюсь, можно
S>синхронизироваться через какой-нибудь соотв. статический примитив.

Не совсем понял вашу мысль...
Мой небогатый опыт взаимодействия с .NET Application Domains показывает, что если создавать AppDomain в потоке UI WinForms (как во втором листинге с закомментированным Thread t = new Thread...), то код в нем будет исполнятся в том же потоке UI WinForms (и, соответственно, фризить форму). Это решает мою проблему, но хотелось бы показывать пользователю прогресс выполнения и дать возможность отмены.

Или вы предлагаете использовать какой-нибудь механизм асинхронных вызовов из .NET Framework, типа BackgroundWorker или AsyncOperation? Эти варианты мне не подходят, так как мне нужно работать с потоком, у которого ApartmentState = STA. А у потоков из пула .NET ApartmentState = MTA.

Не могли бы вы пояснить ваше предложение подробнее?
Re[3]: Асинхронное выполнение длительной операции в другом AppDomain
От: Sharov Россия  
Дата: 06.06.13 11:27
Оценка:
Здравствуйте, Shefango, Вы писали:

S>Не могли бы вы пояснить ваше предложение подробнее?


Неточно выразился.
Попробуйте перенести логику создания потока в методы AbstractImport'а.
Кодом людям нужно помогать!
Re[4]: Асинхронное выполнение длительной операции в другом AppDomain
От: Shefango  
Дата: 06.06.13 12:02
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Здравствуйте, Shefango, Вы писали:


S>>Не могли бы вы пояснить ваше предложение подробнее?


S>Попробуйте перенести логику создания потока в методы AbstractImport'а.


Последовал вашему совету, получилось так

Form1.cs
private void startButton_Click(object sender, EventArgs e)
{
    string assembly = Environment.CurrentDirectory + "\\Import.dll";
    
    if (File.Exists(assembly))
    {
        StartImport(assembly);
    }
}

private void StartImport(object parameter)
{
    Trace.WriteLine(AppDomain.CurrentDomain.FriendlyName);

    string assembly = (string)parameter;
    AppDomain worker = AppDomain.CreateDomain("Worker");
            
    import = (AbstractImport)worker.CreateInstanceFromAndUnwrap(assembly, "Import.Import1CtoTS");

    if (import != null)
    {
        import.Initialize(this);
        import.ProgressChanged += new Shared.ProgressChangedEventHandler(import_ProgressChanged);
        import.WorkCompleted += new WorkCompletedEventHandler(import_WorkCompleted);
        import.Start("ts", "Superuser", "123");
    }

    AppDomain.Unload(worker);
}


AbstractImport.cs
public abstract class AbstractImport : MarshalByRefObject
{
    private Control owner;
    protected bool cancellationPending = false;
        
    public event ProgressChangedEventHandler ProgressChanged;
    public event WorkCompletedEventHandler WorkCompleted;
        
    abstract public void Start(string config, string user, string pass);

    public void Initialize(Control owner)
    {
        this.owner = owner;
    }

    public abstract void Cancel();
}


Import.cs
public class Import1CtoTS : AbstractImport
{
    public Import1CtoTS()
    {
        Trace.WriteLine("Конструктор Import1CtoTS");
    }

    public override void Start(string config, string user, string pass)
    {
        try
        {
            Thread t = new Thread(StartInternal);
            t.SetApartmentState(ApartmentState.STA);
            t.Start();
        }
        catch (Exception ex)
        {
            Trace.WriteLine(ex.Message);
        }
    }

    private void StartInternal()
    {
        string config = "ts";
        string user = "Superuser";
        string pass = "123";
            
        try
        {
            Trace.WriteLine("Зашли в поток!");
            Trace.WriteLine(AppDomain.CurrentDomain.FriendlyName);

            OnWorkCompleted(cancellationPending);
        }
        catch (Exception ex)
        {
            Trace.WriteLine(ex.Message);
        }
    }

    public override void Cancel()
    {
        cancellationPending = true;
    }
}


При таком варианте у меня не запускается метод StartInternal. (Не отрабатывает Trace.WriteLine("Зашли в поток!")
Re: Асинхронное выполнение длительной операции в другом AppDomain
От: TK Лес кывт.рф
Дата: 06.06.13 19:02
Оценка:
Здравствуйте, Shefango, Вы писали:

S>Доброго времени суток.

S>Столкнулся с такой проблемой. Мне нужно провести обмен данными с одной системой, предоставляющей только COM-интерфейс. Делаю я это так:

1. Посмотрите в журнале windows — там вполне может быть информация о возникшем исключении.
2. Зачем вам sta модель, рассчитываете на то, что AppDomian.Unload будет крутить цикл выборки сообщений? Или может finalizer святым духом его воскресит и вызовет там Release для "подвисшего" COM объекта?
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[2]: Асинхронное выполнение длительной операции в другом AppDomain
От: Shefango  
Дата: 07.06.13 04:39
Оценка:
Здравствуйте, TK, Вы писали:

TK>Здравствуйте, Shefango, Вы писали:


S>>Доброго времени суток.

S>>Столкнулся с такой проблемой. Мне нужно провести обмен данными с одной системой, предоставляющей только COM-интерфейс. Делаю я это так:

TK>1. Посмотрите в журнале windows — там вполне может быть информация о возникшем исключении.

Это было сделано в первую очередь. Приложение падает с кодом исключения 0x0eedfade. Нагуглить по этому коду ничего толкового не удалось. В подавляющем большинстве случаев даются только общие рекомендации: переустановить проблемное приложение, пройтись антивирусом, выполнить проверку файлов Windows (sfc /scannow) и переустановить Windows.

TK>2. Зачем вам sta модель, рассчитываете на то, что AppDomian.Unload будет крутить цикл выборки сообщений? Или может finalizer святым духом его воскресит и вызовет там Release для "подвисшего" COM объекта?


STA модель нужна для того, что нужный COM-компонент работает исключительно в ней.
Проблема вот в чем. Этот компонент сам по себе течет по памяти. Если им прокачать большой объем данных, то он валится с OutOfMemory. Следовательно, компонент нужно как-то выгружать. Насколько я знаю, выгрузить отдельную сборку из запущенного приложения нельзя, но можно выгрузить домен приложения, в который она загружена.
Идея была такая: вынести все связанное с импортом в отдельную сборку, загружать ее в отдельном домене приложений Worker, импортировать 1000 записей, выгружать домен приложений Worker (полагая, что выгрузится и COM-компонент), снова загружать сборку с кодом импорта в домен Worker, импортировать 1000 и так далее.
Ну и плюс к этому хотелось дать пользователю возможность прерывания импорта и просмотра прогресса. Поэтому импорт запускается в отдельном потоке.

Мне непонятно вот что: если импорт запускать один раз, то все работает без ошибок. Если два и более раз подряд — то валится с исключением.
Re[3]: Асинхронное выполнение длительной операции в другом AppDomain
От: aloch Россия  
Дата: 07.06.13 04:56
Оценка: +1
Здравствуйте, Shefango, Вы писали:

Совсем не факт, что выгрузка текущего по памяти COM-компонента (который про домены приложений и .Net вообще ничего не знает) приведет к тому, что утекшая память будет освобождена.

Я бы сделал отдельный ПРОЦЕСС, использующий этот компонент. Запускал бы его для импорта 1000 объектов, убивал, и запускал снова. Взаимодействовать с процессом можно через объекты Windows (события и т.п.)


Re[3]: Асинхронное выполнение длительной операции в другом AppDomain
От: TK Лес кывт.рф
Дата: 07.06.13 05:10
Оценка: +1
Здравствуйте, Shefango, Вы писали:

S>STA модель нужна для того, что нужный COM-компонент работает исключительно в ней.

S>Проблема вот в чем. Этот компонент сам по себе течет по памяти. Если им прокачать большой объем данных, то он валится с OutOfMemory. Следовательно, компонент нужно как-то выгружать. Насколько я знаю, выгрузить отдельную сборку из запущенного приложения нельзя, но можно выгрузить домен приложения, в который она загружена.

Если ваш компонент течет unmanaged памятью то (компонент у вас на чем написан?), выгрузка домена его никак от этого не спасет — тут надо выносить в отдельный процесс.
Если вы корректно освобождете все ссылка на объект (Marshal.ReleaseComObject) то, особой нужды в AppDomain нет. Если нет, то стоит задуматься на тему как finalizer thread будет взаимодействовать с возданным вами sta апартментом (и когда это может произойти)
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[4]: Асинхронное выполнение длительной операции в другом AppDomain
От: Shefango  
Дата: 07.06.13 05:18
Оценка:
Здравствуйте, aloch, Вы писали:

A>Здравствуйте, Shefango, Вы писали:


A>Совсем не факт, что выгрузка текущего по памяти COM-компонента (который про домены приложений и .Net вообще ничего не знает) приведет к тому, что утекшая память будет освобождена.

В общем, вы правы. Однозначного ответа на вопрос будет ли освобождаться память при выгрузке домена приложения, в который загружена сборка обвязки COM-компонента мне не удалось. Поэтому решил сам попробовать.

A>Я бы сделал отдельный ПРОЦЕСС, использующий этот компонент. Запускал бы его для импорта 1000 объектов, убивал, и запускал снова. Взаимодействовать с процессом можно через объекты Windows (события и т.п.)


Спасибо за совет, я и сам подумывал так сделать если не выгорит вариант с доменом приложения.
Re[4]: Асинхронное выполнение длительной операции в другом AppDomain
От: Shefango  
Дата: 07.06.13 05:55
Оценка:
Здравствуйте, TK, Вы писали:

TK>Здравствуйте, Shefango, Вы писали:


S>>STA модель нужна для того, что нужный COM-компонент работает исключительно в ней.

S>>Проблема вот в чем. Этот компонент сам по себе течет по памяти. Если им прокачать большой объем данных, то он валится с OutOfMemory. Следовательно, компонент нужно как-то выгружать. Насколько я знаю, выгрузить отдельную сборку из запущенного приложения нельзя, но можно выгрузить домен приложения, в который она загружена.

TK>Если ваш компонент течет unmanaged памятью то (компонент у вас на чем написан?), выгрузка домена его никак от этого не спасет — тут надо выносить в отдельный процесс.

TK>Если вы корректно освобождете все ссылка на объект (Marshal.ReleaseComObject) то, особой нужды в AppDomain нет. Если нет, то

На чем написан компонет с точностью сказать не могу, но скорее всего на Delphi/C++ Builder.

Ссылки освобождаются корректно, проверено путем логгирования в конструкторах и финализаторах классов-обвязок над интерфейсами предоставленными компонентом. Сколько их создается, столько и освобождается, Marshal.ReleaseComObject везде возвращает 0. Но при этом объем памяти процесса со временем все равно растет. Точнее так: перед запуском импорта память — 30 Мб, во время шага импорта — 180 Мб, после завершения шага — 35. После каждого шага импорта количество занимаемой памяти уввеличивается.

TK>стоит задуматься на тему как finalizer thread будет взаимодействовать с возданным вами sta апартментом (и когда это может произойти)

По идее сборщик мусора должен отработать перед выгрузкой домена. В том, что выгрузка домена повлечет освобождение unmanaged памяти я не уверен.

А с STA сборка мусора в этом случае как-то же проходит:
class Program
{
    [STAThread]
    static void Main()
    {

    }
}
Re[5]: Асинхронное выполнение длительной операции в другом AppDomain
От: TK Лес кывт.рф
Дата: 07.06.13 07:58
Оценка:
Здравствуйте, Shefango, Вы писали:

S>А с STA сборка мусора в этом случае как-то же проходит:


STA это для COM, а не для сборщика мусмора. При этом, если сборщику мусора потребуется освободить COM объект созданный в STA то, по правилам ему надо будет сделать это в STA апартменте (т.е. вызов Release надо отмаршалить в нужный поток).
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[6]: Асинхронное выполнение длительной операции в другом AppDomain
От: Shefango  
Дата: 10.06.13 03:38
Оценка:
Спасибо всем за помощь!
Но перенес выгрузку в отдельную сборку. Коммуникацию между сборками осуществляю посредством WCF.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.