Рекурсивно вызывать метод с тем же потоком - PullRequest
0 голосов
/ 20 марта 2020

У меня есть следующий метод:

public async Task ScrapeObjects(int page = 1)
{
    try
    {
        while (!isObjectSearchCompleted)
        {
            ..do calls..
        }
    }
    catch (HttpRequestException ex)
    {

        Thread.Sleep(TimeSpan.FromSeconds(60));
        ScrapeObjects(page);

        Log.Fatal(ex, ex.Message);
    }
}

Я называю этот длительный метод asyn c, и я не жду, пока он завершится sh. Дело в том, что исключение может произойти, и в этом случае я хочу справиться с этим. Но тогда я хочу начать с того места, где я ушел, и с той же темы. В текущем состоянии новый поток используется, когда я рекурсивно вызываю метод после обработки исключения. Я хотел бы продолжать использовать ту же тему. Есть ли способ сделать это? Спасибо!

Ответы [ 2 ]

1 голос
/ 20 марта 2020

Вам, вероятно, нужно переместить блок try / catch внутри while l oop и добавить счетчик с ошибками, чтобы выручить в случае непрерывных неудачных попыток.

public async Task ScrapeObjects()
{
    int failedCount = 0;
    int page = 1;
    while (!isObjectSearchCompleted)
    {
        try
        {
            //..do calls..
        }
        catch (HttpRequestException ex)
        {
            failedCount++;
            if (failedCount < 3)
            {
                Log.Info(ex, ex.Message);
                await Task.Delay(TimeSpan.FromSeconds(60));
            }
            else
            {
                Log.Fatal(ex, ex.Message);
                throw; // or return;
            }
        }
    }
}

В качестве примечания, как правило, лучше await Task.Delay вместо Thread.Sleep внутри асинхронных методов, чтобы избежать блокировки потока без причины.

0 голосов
/ 20 марта 2020

Один простой вопрос, прежде чем читать длинный ответ ниже:

Зачем вам нужна такая же тема? Получаете ли вы доступ к потоку stati c / contextual data?

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

Как ограничить выполнение задач в одном потоке

Пока вы используете вызовы asyn c в контексте синхронизации по умолчанию и как только код возобновляется из await, возможно, что поток может измениться после await. Это связано с тем, что контекст по умолчанию планирует задачи для следующего доступного потока в пуле потоков. Как и в приведенном ниже случае, before может отличаться от after:

public async Task ScrapeObjects(int page = 1)
{
    var before = Thread.CurrentThread.ManagedThreadId;
    await Task.Delay(1000);
    var after = Thread.CurrentThread.ManagedThreadId;
}

Единственный надежный способ гарантировать, что ваш код может вернуться в тот же поток, - это запланировать асинхронное выполнение c код для однопоточного контекста синхронизации:

class SingleThreadSynchronizationContext : SynchronizationContext
{ 
    private readonly BlockingCollection<Action> _actions = new BlockingCollection<Action>();
    private readonly Thread _theThread;

    public SingleThreadSynchronizationContext()
    {
        _theThread = new Thread(DoWork);
        _theThread.IsBackground = true;
        _theThread.Start();
    }

    public override void Send(SendOrPostCallback d, object state)
    {
        // Send requires run the delegate immediately.
        d(state);
    }

    public override void Post(SendOrPostCallback d, object state)
    {
        // Schedule the action by adding to blocking collection.
        _actions.Add(() => d(state));
    }

    private void DoWork()
    {
        // Keep picking up actions to run from the collection.
        while (!_actions.IsAddingCompleted)
        {
            try
            {
                var action = _actions.Take();
                action();
            }
            catch (InvalidOperationException)
            {
                break;
            }
        }       
    }
}

И вам нужно запланировать ScrapeObjects в пользовательский контекст:

SynchronizationContext.SetSynchronizationContext(new SingleThreadSynchronizationContext());
await Task.Factory.StartNew(
        () => ScrapeObjects(),
        CancellationToken.None,
        TaskCreationOptions.DenyChildAttach | TaskCreationOptions.LongRunning,
        TaskScheduler.FromCurrentSynchronizationContext()
).Unwrap();

Таким образом, весь ваш асин c код должен быть запланирован на тот же контекст и выполняться потоком в этом контексте.

Однако

Это обычно опасно , как вы вдруг потерять способность использовать пул потоков. Если вы заблокируете поток, вся асинхронная операция c будет заблокирована, то есть у вас будут взаимоблокировки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...