递归调用具有相同线程的方法

时间:2020-03-19 22:00:37

标签: c# multithreading .net-core

我有以下方法:

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);
    }
}

我将此长时间运行的方法称为异步方法,我不等待它完成。问题是发生了异常,在这种情况下,我想处理它。但是然后我想从我离开的地方开始,并使用相同的线程。在当前状态下,当我在处理异常后递归调用该方法时,将使用新线程。我想继续使用相同的线程。有办法吗?谢谢!

2 个答案:

答案 0 :(得分:1)

您可能需要在while循环内移动try / catch块,并添加一个计数器,该计数器会显示发生的错误,以防连续尝试失败。

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,以避免无故阻塞线程。

答案 1 :(得分:0)

在阅读下面的详细答案之前,有一个简单的问题:

为什么需要相同的线程?您是否正在访问线程静态/上下文数据?

如果是的话,将有比限制您的任务在同一线程上运行更容易解决的问题。

如何限制任务在单个线程上运行

只要您在默认的同步上下文上使用异步调用,并且一旦从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;
}

保证代码可以返回同一线程的唯一可靠方法是将异步代码安排在单个线程同步上下文中:

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();

这样做,所有异步代码都应安排在相同的上下文中,并由该线程在该上下文中运行。

但是

这通常是危险,因为您突然失去了使用线程池的能力。如果您阻塞线程,则整个异步操作都会被阻塞,这意味着您将出现死锁。