Потокобезопасное чтение из ресурса
От: Neco  
Дата: 21.05.13 05:45
Оценка: :)
Чё-т торможу, не могу вкурить как написать правильно.

    public class MainClass : IDisposable
    {
        private Thread _th;
        private UnmanagerResource _res;
        private object _syncObj = new object();

        public MainClass()
        {
            _res = new UnmanagerResource();
            StartListen();
        }
        private void StartListen()
        {
            _th = new Thread(() =>
            {
                while (true)
                {
                    // ---------- как (и надо ли?) убедиться здесь, что ресурс доступен (Dispose к нему не был вызван и он не null)?
                    var data = _res.Read();
                    // do someting with the data
                }
            });
            _th.Start();
        }
        private void StopListen()
        {
            _th.Abort();
            _th = null;
        }
        public void Dispose()
        {
            lock (_syncObj)
            {
                if (_res != null)
                {
                    StopListen(); // гарантирует ли Thread.Abort, что строка с _res.Read не будет вызвана при _res = null?
                    _res.Dispose();
                    _res = null;
                }
            }
        }
    }
    public class UnmanagerResource : IDisposable
    {
        public string Read()
        {
            int data = (new Random()).Next(5000, 10000);
            Thread.Sleep(data);
            return data.ToString();
        }
        public void Dispose()
        {
            
        }
    }
всю ночь не ем, весь день не сплю — устаю
Re: Потокобезопасное чтение из ресурса
От: Константин Черногория  
Дата: 21.05.13 16:03
Оценка:
Здравствуйте, Neco, Вы писали:

N>Чё-т торможу, не могу вкурить как написать правильно.

Так неправильно.

1. Убивать поток вызовом "Abort()" неправильно.
Если ваш UnmanagerResource это сокет, можете закрыть сокет, сразу после этого Read() выкинет исключение, вам нужно его поймать и написать return из вашего while(true) в треде.
Если не сокет — можете например создать ManualResetEventSlim, и вместо while(true) написать while(!m_shouldStopEvent.IsSet), но тогда вам нужно или гарантировать шо Read() регулярно шо-то возвращает, или прикручивать к Read() поддержку cancellation или таймаутов.

2. У вас нет проверки на то что StartListen будет вызван несколько раз. В этом случае у вас будет несколько тредов, и скорее всего всё сломается.

3. Использование Thread + blocking IO для подобных задач вообще редко хорошая идея. Смотрите лучше в сторону async-await.
Re[2]: Потокобезопасное чтение из ресурса
От: Neco  
Дата: 21.05.13 18:11
Оценка:
Здравствуйте, Константин, Спасибо за ответ!

К>1. Убивать поток вызовом "Abort()" неправильно.

да, эт знаю.

К>Если ваш UnmanagerResource это сокет, можете закрыть сокет, сразу после этого Read() выкинет исключение, вам нужно его поймать и написать return из вашего while(true) в треде.

нет, у меня это WebSphere MQ Queue. Как оказалось, если вошло в Read, то Close виснет вообще.

К>Если не сокет — можете например создать ManualResetEventSlim, и вместо while(true) написать while(!m_shouldStopEvent.IsSet), но тогда вам нужно или гарантировать шо Read() регулярно шо-то возвращает, или прикручивать к Read() поддержку cancellation или таймаутов.

не вполне понял. если надо что-то докручивать в самом UnmanagedResource, то опять же — он у меня настолько неуправляемый, что даже переписыванию не поддаётся.

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

            _listenThread = new Thread(() =>
            {
                try
                {
                    while (true)
                    {
                        MQQueue qIn = null;
                        try
                        {
                            qIn = _managerIn.AccessQueue(_inQueue, MQC.MQOO_INPUT_AS_Q_DEF | +MQC.MQOO_FAIL_IF_QUIESCING);

                            var msg = new MQMessage();

                            qIn.Get(msg, options);
                            onMessageArrived(msg);
                        }
                        catch (MQException ex)
                        {
                            if (ex.ReasonCode != 2033)
                            {
                                throw ex;
                            }
                        }
                        finally
                        {
                            if (qIn != null)
                            {
                                qIn.Close();
                            }
                        }
                        if (_needToStop)
                            break;
                    }
                }
                catch (Exception ex)
                {
                    if (!(ex is ThreadAbortException))
                    {
                        onListeningFailed(ex);
                    }
                }
            });

теперь тот же вопрос касательно _managerIn — он у меня в Dispose диспозится и обнуляется. Конечно StopListen вызывается до этого и вероятно никогда ничего плохого не случится (т.е. к моменту вызова _managerIn.Access менеджер всегда будет not null), но не покидает ощущение зыбкости. А lock ставить внутри while(true) почему-то совсем не хочется.

К>2. У вас нет проверки на то что StartListen будет вызван несколько раз. В этом случае у вас будет несколько тредов, и скорее всего всё сломается.

да вроде там только из конструктора вызываю.
в любом случае, это был просто пример кода — чтобы вопрос задать. как вы понимаете, вопрос был по другой части кода.

К>3. Использование Thread + blocking IO для подобных задач вообще редко хорошая идея. Смотрите лучше в сторону async-await.

я пока на четвёртом дотнете сижу (никак не вылезу). может и можно как-то прикрутить, но не до сук.
всю ночь не ем, весь день не сплю — устаю
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.