You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Timer触发异步任务异常:API调用重复执行问题排查

Timer触发异步API调用时重复执行的异常原因及解决办法

问题现象

编写了定时调用OpenSea API的脚本,使用System.Timers.Timer每隔1秒触发一次异步任务TimerElapsedAsync。但执行到API调用的await openSeaApiClient.GetBestListingsInCollectionResponse(CollectionName)时,控制台会连续打印递增的Counter值(如Counter:2、3、4...),调试时也会持续触发Timer事件,无法正常推进后续流程。

核心代码片段

Program类代码

public class Program
{
    private static OpenSeaApiClient openSeaApiClient;
    private static Timer timer;
    private static int counter = 1;
    private static Collection galaxyEggs9999;

    static async Task Main()
    {
        string apiKey = "myKey";
        openSeaApiClient = new OpenSeaApiClient(apiKey);

        // 设置定时器每1秒执行一次API调用
        timer = new Timer(TimeSpan.FromSeconds(1).TotalMilliseconds);
        timer.Elapsed += async (sender, e) => await TimerElapsedAsync();
        timer.Start();

        Console.WriteLine("Press Enter to exit.");
        Console.ReadKey();

        timer.Stop();
        timer.Dispose();
    }

    private static async Task TimerElapsedAsync()
    {
        Console.WriteLine($"Counter: {counter++}");

        if (galaxyEggs9999 == null)
        {
            galaxyEggs9999 = new Collection(openSeaApiClient, "galaxyeggs9999", "0xA08126f5E1ED91A635987071E6FF5EB2aEb67C48");
        }
        await galaxyEggs9999.ExecuteAsync();
        // 其他Collection实例执行逻辑
    }
}

Collection类核心代码

public class Collection
{
    private readonly OpenSeaApiClient openSeaApiClient;
    private readonly string collectionName;
    private readonly string collectionContractAddress;

    public Collection(OpenSeaApiClient openSeaApiClient, string collectionName, string collectionContractAddress)
    {
        this.openSeaApiClient = openSeaApiClient;
        this.collectionName = collectionName;
        this.collectionContractAddress = collectionContractAddress;
    }

    public async Task ExecuteAsync()
    {
        // 其他逻辑...
        else 
        {
            var bestListingsResponse = await openSeaApiClient.GetBestListingsInCollectionResponse(collectionName);
            floorPriceInEtherDouble = GetCollectionFloorPriceInEther(bestListingsResponse);
            // 其他逻辑...
        }
    }
}

OpenSeaApiClient核心方法

public async Task<string> GetBestListingsInCollectionResponse(string collectionSlug)
{
    string endpoint = $"api/v2/listings/collection/{collectionSlug}/best";
    return await SendGetRequest(endpoint);
}

原因分析

  1. 异步void事件处理的特性:System.Timers.Timer的Elapsed事件期望返回void的委托,代码中使用的async (sender, e) => await TimerElapsedAsync()本质是异步void方法。这类方法在遇到第一个await(即API调用的异步等待)时会立即返回,Timer会判定当前事件处理已完成,到达下一个间隔就会再次触发事件。
  2. Timer的重入特性:默认情况下System.Timers.Timer允许重入——前一次事件处理还未完成,到达设定间隔后会再次触发新的事件。而API调用属于耗时操作,await会释放当前线程,Timer到点就会再次执行TimerElapsedAsync,导致多个任务并发执行,因此Counter会连续递增。

解决办法

方法一:禁用Timer自动重置,手动重启

将Timer的AutoReset设为false,让Timer只触发一次,在异步任务完成后手动重启Timer,确保每次任务执行完成后才会触发下一次:

static async Task Main()
{
    string apiKey = "myKey";
    openSeaApiClient = new OpenSeaApiClient(apiKey);

    timer = new Timer(TimeSpan.FromSeconds(1).TotalMilliseconds);
    timer.AutoReset = false; // 关闭自动重置,只触发一次
    timer.Elapsed += async (sender, e) => await TimerElapsedAsync();
    timer.Start();

    Console.WriteLine("Press Enter to exit.");
    Console.ReadKey();

    timer.Stop();
    timer.Dispose();
}

private static async Task TimerElapsedAsync()
{
    Console.WriteLine($"Counter: {counter++}");

    if (galaxyEggs9999 == null)
    {
        galaxyEggs9999 = new Collection(openSeaApiClient, "galaxyeggs9999", "0xA08126f5E1ED91A635987071E6FF5EB2aEb67C48");
    }
    await galaxyEggs9999.ExecuteAsync();
    // 任务完成后重启Timer
    timer.Start();
}

方法二:用信号量控制并发

使用SemaphoreSlim限制同一时间只有一个任务在执行,避免Timer重入:

public class Program
{
    private static OpenSeaApiClient openSeaApiClient;
    private static Timer timer;
    private static int counter = 1;
    private static Collection galaxyEggs9999;
    private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); // 最多允许1个并发任务

    static async Task Main()
    {
        string apiKey = "myKey";
        openSeaApiClient = new OpenSeaApiClient(apiKey);

        timer = new Timer(TimeSpan.FromSeconds(1).TotalMilliseconds);
        timer.Elapsed += async (sender, e) => await TimerElapsedAsync();
        timer.Start();

        Console.WriteLine("Press Enter to exit.");
        Console.ReadKey();

        timer.Stop();
        timer.Dispose();
        _semaphore.Dispose();
    }

    private static async Task TimerElapsedAsync()
    {
        // 尝试获取信号量,若已被占用则直接返回,不执行本次任务
        if (!_semaphore.Wait(0))
            return;
        
        try
        {
            Console.WriteLine($"Counter: {counter++}");

            if (galaxyEggs9999 == null)
            {
                galaxyEggs9999 = new Collection(openSeaApiClient, "galaxyeggs9999", "0xA08126f5E1ED91A635987071E6FF5EB2aEb67C48");
            }
            await galaxyEggs9999.ExecuteAsync();
        }
        finally
        {
            // 释放信号量,允许下一次任务执行
            _semaphore.Release();
        }
    }
}

内容的提问来源于stack exchange,提问作者paro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 18:58:10