Обернуть асинхронный метод .NET Remoting в задачу TPL - PullRequest
0 голосов
/ 05 ноября 2018

У нас есть устаревшее приложение на основе .NET Remoting. Наша клиентская клиентская библиотека в настоящее время поддерживает только синхронные операции. Я хотел бы добавить асинхронные операции с методами async Task<> на основе TPL.

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

Я также нашел эту статью , которая описывает, как преобразовать асинхронные операции на основе APM в асинхронные задачи на основе TPL (с использованием Task.Factory.FromAsync)

В чем я не уверен, так это в том, обязан ли я указывать функцию обратного вызова в .BeginInvoke(), а также указывать .EndInvoke(). Если требуется и то и другое, в чем именно заключается разница между функцией обратного вызова и .EndInvoke(). Если требуется только один, который я должен использовать для возврата значений, а также убедиться, что у меня нет утечек памяти .

Вот мой текущий код, в котором я не передаю обратный вызов .BeginInvoke():

public class Client : MarshalByRefObject
{
    private IServiceClass service;

    public delegate double TimeConsumingCallDelegate();

    public void Configure()
    {
        RemotingConfiguration.Configure("client.exe.config", false);

        var wellKnownClientTypeEntry = RemotingConfiguration.GetRegisteredWellKnownClientTypes()
            .Single(wct => wct.ObjectType.Equals(typeof(IServiceClass)));

        this.service = Activator.GetObject(typeof(IServiceClass), wellKnownClientTypeEntry.ObjectUrl) as IServiceClass;
    }

    public async Task<double> RemoteTimeConsumingRemoteCall()
    {
        var timeConsumingCallDelegate = new TimeConsumingCallDelegate(service.TimeConsumingRemoteCall);

        return await Task.Factory.FromAsync
            (
                timeConsumingCallDelegate.BeginInvoke(null, null),
                timeConsumingCallDelegate.EndInvoke
           );
    }

    public async Task RunAsync()
    {
        var result = await RemoteTimeConsumingRemoteCall();
        Console.WriteLine($"Result of TPL remote call: {result} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
    }
}

public class Program
{
    public static async Task Main(string[] Args)
    {
        Client clientApp = new Client();
        clientApp.Configure();

        await clientApp.RunAsync();

        Console.WriteLine("Press any key to continue...");
        Console.ReadKey(false);
    }
}

Ответы [ 2 ]

0 голосов
/ 05 ноября 2018

Разница между функцией обратного вызова и .EndInvoke() заключается в том, что обратный вызов будет выполняться в произвольном потоке из пула потоков. Если вы должны быть уверены, что получили результат чтения в том же потоке, что и тот, который вы назвали BeginInvoke, то вам не следует использовать обратный вызов, а опросить объект IAsyncResult и вызвать .EndInvoke(), когда операция будет завершена.

Если вы позвоните .EndInvoke() сразу после .Beginnvoke(), вы заблокируете поток, пока операция не завершится. Это будет работать, но будет плохо масштабироваться.

Итак, то, что ты делаешь, кажется нормальным!

0 голосов
/ 05 ноября 2018

Вы должны указать функцию обратного вызова для повышения эффективности. Если FromAsync имеет только IAsyncResult для работы, он не может быть уведомлен о завершении этого асинхронного результата. Придется использовать событие для ожидания. Это блокирует поток (или регистрирует ожидание пула потоков, что не так уж и плохо).

Эффективный асинхронный ввод-вывод требует обратных вызовов на некотором уровне.

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

Если у вас мало или долго выполняются вызовы, async никак не поможет вам с производительностью, но все же может упростить программирование с помощью графического интерфейса.

Так что это не правильно:

public async Task<double> RemoteTimeConsumingRemoteCall()
{
    var timeConsumingCallDelegate = new TimeConsumingCallDelegate(service.TimeConsumingRemoteCall);

    return await Task.Factory.FromAsync
        (
            timeConsumingCallDelegate.BeginInvoke(null, null),
            timeConsumingCallDelegate.EndInvoke
       );
}

Чтобы убедиться, что ваша реализация действительно не блокирует какой-либо поток, я бы сделал это: вставьте Thread.Sleep(100000) на стороне сервера и выполните 1000 одновременных вызовов на клиенте. Вы должны обнаружить, что количество потоков не увеличивается.

...