0

Задача В .net есть возможность звать делегаты как синхронно:

EventHandler h = new EventHandler(this.myEventHandler); 
h.Invoke(null, EventArgs.Empty); 
так и асинхронно:
var res = h.BeginInvoke(null, EventArgs.Empty, null, null);

Нужно реализовать возможность полусинхронного вызова делегата (написать реализацию класса AsyncCaller), который бы работал таким образом:

EventHandler h = new EventHandler(this.myEventHandler); 
ac = new AsyncCaller(h); 
bool completedOK = ac.Invoke(5000, null, EventArgs.Empty);

Полусинхронного в данном случае означает, что делегат будет вызван, и вызывающий поток будет ждать, пока вызов не выполнится. Но если выполнение делегата займет больше 5000 миллисекунд, то ac.Invoke выйдет и вернет в completedOK значение false.

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

aepot
  • 49,560
DanBit
  • 123
  • Как вы в асинхронность варианте поймете, что делегаты еще не выполнился или уже выполнился? Коллбеки будете использовать? Если вам надо ожидать чего то, почему бы не ожидать асинхроннно? – tym32167 Feb 18 '21 at 18:20
  • 1
    Я когда то баловался вот таким, может вас на какие мысли натолкнет. – tym32167 Feb 18 '21 at 18:21
  • Такое вот задание, я думаю может в торону класса Action посмотеть и в нем метод invoke переопределить – DanBit Feb 18 '21 at 19:10

1 Answers1

2

Вы просите странного, Delegate.BeginInvoke не поддерживается со времен .NET Core 2.0, то есть достаточно давно. EAP устарел, позвольте ему покоиться с миром. Пора бы уже изучать TAP.

Но если очень хочется, то почему бы и нет.

class Program
{
    private void MyEventHandler(object sender, EventArgs e)
    {
        Console.WriteLine("Enter Handler");
        Thread.Sleep(1000);
        Console.WriteLine("Exit Handler");
    }
private void Run()
{
    EventHandler h = new EventHandler(MyEventHandler);
    AsyncCaller ac = new AsyncCaller(h);
    if (ac.Invoke(5000, this, EventArgs.Empty))
        Console.WriteLine("Completed successfully");
    else
        Console.WriteLine("Timeout occured");
}

static void Main(string[] args)
{
    new Program().Run();
    Console.WriteLine("Done.");
    Console.ReadKey();
}

}

public class AsyncCaller { private readonly EventHandler handler; private Thread thread;

public AsyncCaller(EventHandler handler)
{
    this.handler = handler;
}

private void Aborter(IAsyncResult ar)
{
    thread?.Abort();
}

private void Wait(object timeout)
{
    Thread.Sleep((int)timeout);
}

public bool Invoke(int timeout, object sender, EventArgs e)
{
    thread = new Thread(Wait);
    IAsyncResult asyncResult = handler?.BeginInvoke(sender, e, Aborter, this);
    thread.Start(timeout);
    thread.Join();
    thread = null;
    return asyncResult.IsCompleted;
}

}

Запускаем делегат, который работает секунду, получаем:

Enter Handler
Exit Handler
Completed successfully
Done.

Меняем 1000 на 6000, получаем

Enter Handler
Timeout occured
Done.
Exit Handler

А теперь то же самое, только с использованием TAP

private static async Task HandleAsync()
{
    Console.WriteLine("Enter Handler");
    await Task.Delay(1000);
    Console.WriteLine("Exit Handler");
}

static async Task Main(string[] args) { Task t = HandleAsync(); if (await Task.WhenAny(t, Task.Delay(5000)) == t) Console.WriteLine("Completed successfully"); else Console.WriteLine("Timeout occured"); Console.WriteLine("Done."); Console.ReadKey(); }

Вывод при аналогичных ожиданиях такой же.


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

aepot
  • 49,560
  • я верно понял, в первом случае у вас ожидение всегда будет минимум 5 секунд, незваисимо от скорости асинхронного вызова? – tym32167 Feb 18 '21 at 20:09
  • @tym32167 нет, не верно поняли :) – aepot Feb 18 '21 at 20:10
  • 1
    а, точно, пропустил Aborter – tym32167 Feb 18 '21 at 20:15
  • Thread.Abort? o_O – VladD Feb 18 '21 at 21:30
  • @VladD ну можно SpinWait какой-нибудь крутить, но суть от этого не поменяется. В самом конце ответа легкий дислеймер, который это замечание в том числе в себя включает. – aepot Feb 18 '21 at 21:31
  • @aepot: https://docs.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/5.0/thread-abort-obsolete – VladD Feb 18 '21 at 21:32
  • @VladD эммм. А Delegate.BeginInvoke никто не заметил? Который был выпилен вообще 100 лет назад ))) – aepot Feb 18 '21 at 21:33
  • @aepot: Это правда, но Thread.Abort был ужасен всегда. Вот ещё пример: https://docs.microsoft.com/ru-ru/archive/blogs/ericlippert/locks-and-exceptions-do-not-mix – VladD Feb 18 '21 at 21:36
  • @VladD да тут весь ответ ужасен как всё EAP полностью вместе взятое :) – aepot Feb 18 '21 at 21:40
  • @aepot: Или вот это попробуйте: https://pastebin.com/TgzNNqB5 – VladD Feb 18 '21 at 21:42
  • @VladD ахахах) ну да, так оно работает (работало). На практике я никогда не юзал Abort и не планирую. Я вообще не юзаю Thread, потому что есть Task и CancellationToken. – aepot Feb 18 '21 at 21:48
  • Там недавно в .NET 6 добавили фичу WaitAsync(TimeSpan), если хотите можете добавить/изменить ответ – Aarnihauta Aug 31 '22 at 19:48