Начните и завершите Invoke в одном методе расширения должным образом - PullRequest
3 голосов
/ 23 мая 2011

Я хочу перепроверить с другими, является ли это правильным способом создания метода расширения, который запускает асинхронный процесс и возвращает функцию, которая при вызове, по существу, ожидает этого процесса и получает результат.

    public static Func<R> HandleInvoke<T, R>(this Func<T, R> function, T arg, Action<IAsyncResult> callback)
    {
        IAsyncResult result = function.BeginInvoke(arg, new AsyncCallback(callback), function);

        return delegate
        {
            return function.EndInvoke(result);
        };
    }

По сути, я хочу использовать его так (псевдокод):

Func<R> myFunc = (some delegate).HandleInvoke(arg, callback);  
                     // at this point the operation begins, but will be nonblocking

                     // do other stuff

var result = myFunc();   // now I am deciding to wait on the result, which is blocking

Не был уверен, стоит ли мне беспокоиться об ожидании ожидания в этой ситуации или нет. Также не уверен, будет ли вообще необходим обратный вызов. Также я думаю, что это закрытие?

EDIT

Закончилось этим,

    public static Func<R> HandleInvoke<T, R>(this Func<T, R> function, T arg)
    {
        IAsyncResult asyncResult = function.BeginInvoke(arg, iAsyncResult =>
            {
                if (!(iAsyncResult as AsyncResult).EndInvokeCalled)
                {
                    (iAsyncResult.AsyncState as Func<T, R>).EndInvoke(iAsyncResult);
                }

            }, function); 

        return delegate
        {
            WaitHandle.WaitAll(new WaitHandle[] { asyncResult.AsyncWaitHandle }); 

            return function.EndInvoke(asyncResult); 
        };
    }

Что, кажется, работает хорошо. Обратный вызов проверяет, был ли вызван EndInvoke, и если нет, вызывает его. В противном случае EndInvoke вызывается в возвращенном делегате.

2-е РЕДАКТИРОВАНИЕ

Вот моя последняя попытка - пока не выдавал никаких ошибок и, похоже, хорошо с этим справляется. Я не мог заставить его работать, когда делегат возвращал функцию function.EndInvoke (), но делегат ждет, пока EndInvoke не будет вызван в анонимном обратном вызове, прежде чем вернуть R. Thread.Sleep (), вероятно, не лучшее решение, хотя. Также можно использовать дополнительную проверку, чтобы убедиться, что R фактически назначено в каждом случае.

    public static Func<R> HandleInvoke<T, R>(this Func<T, R> function, T arg)
    {
        R r = default(R); 

        IAsyncResult asyncResult = function.BeginInvoke(arg, result =>
            {
                r = (result.AsyncState as Func<T, R>).EndInvoke(result);

            }, function); 


        return delegate
        {
            while (!(asyncResult as AsyncResult).EndInvokeCalled)
            {
                Thread.Sleep(1); 
            }

            return r; 
        };
    }

Ответы [ 2 ]

2 голосов
/ 23 мая 2011

Это должно работать, но я не заинтересован в дизайне ... Вот основная проблема.

Если вызывается myFunc, вам не следует вызывать EndInvoke в обратном вызове, но если myFunc не вызывается, потому что вас не волнует возвращаемое значение, тогда EndInvoke должен вызываться в обратном вызове. Это делает использование API неочевидным и подверженным ошибкам.

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

    public static Func<R> HandleInvoke<T, R>(this Func<T, R> function, T arg)
    {
        R retv = default(R);
        bool completed = false;

        object sync = new object();

        IAsyncResult asyncResult = function.BeginInvoke(arg, 
            iAsyncResult =>
            {
                lock(sync)
                {
                    completed = true;
                    retv = function.EndInvoke(iAsyncResult);
                    Monitor.Pulse(sync); // wake a waiting thread is there is one
                }
            }
            , null);

        return delegate
        {

            lock (sync)
            {
                if (!completed) // if not called before the callback completed
                {
                    Monitor.Wait(sync); // wait for it to pulse the sync object
                }
                return retv;
            }
        };
    }
1 голос
/ 12 августа 2012

Я наткнулся на более простой способ сделать это, где вы получаете преимущество наличия исключений (пере) броска на сайт вызова вместо фонового потока:

    public static Func<R> Future<T, R>(this Func<T, R> func, T arg)
    {
        IAsyncResult result = func.BeginInvoke(arg, null, null);

        return () => func.EndInvoke(result);
    }

Это почти идентично дляДействие:

    public static Action Future<T>(this Action<T> action, T arg)
    {
        IAsyncResult result = action.BeginInvoke(arg, null, null);

        return () => action.EndInvoke(result);
    }

Но если вам не нужен результат, тогда ThreadPool.QueueUserWorkItem () более эффективен.

...