簡體   English   中英

Observable.Interval 同時執行一個方法兩次

[英]Observable.Interval executes a method twice at the same time

ExecuteSellAsync 方法同時被調用兩次,您可以在下面的日志中看到。 當我在Observable.Interval(TimeSpan.FromSeconds(15))上放置 15 秒時,它工作正常。 我怎樣才能防止這種情況? 也許像鎖定或我不知道。

2021-02-12 19:04:09 [11] DEBUG LiveTradeManager Order ID: 263010769 | Pair: DOGEUSDT | Order side: Sell | Status: New | Price: 0.06783960 | Last filled price: 0.00000000 | Stop price: 0.00000000 | Quantity: 0.00000000 | Quote quantity: 0.00000000 | Commission: 0 
2021-02-12 19:04:09 [11] DEBUG LiveTradeManager Order ID: 263010769 | Pair: DOGEUSDT | Order side: Sell | Status: Filled | Price: 0.06783960 | Last filled price: 0.06784260 | Stop price: 0.00000000 | Quantity: 5420.00000000 | Quote quantity: 367.70689200 | Commission: 0.00201210 BNB
2021-02-12 19:04:09 [11] DEBUG LiveTradeManager Sell order was filled | Close date: 2021/02/12 17:04:09 | Close rate (price): 0.06784260
2021-02-12 19:04:13 [9] INFO  Wallets Wallets synced.
2021-02-12 19:04:14 [10] DEBUG LiveTradeManager Timer triggered. Price: 0.06783910 | Timestamp: 2/12/2021 5:03:00 PM | Close: 0.06790680
2021-02-12 19:04:17 [9] DEBUG BinanceSpotClient Limit sell order has failed | Error code: -2010 | Error message: Account has insufficient balance for requested action. | Pair: DOGEUSDT | Quantity: 0.00000000 | Price: 0.06782540

_throttlerObservable = Observable.Interval(TimeSpan.FromSeconds(5))
   .SelectMany(_ => Observable.FromAsync(async () =>
   {
       var lastCandle = _candles.Last();

       _logger.Debug($"Timer triggered. Price: {_ticker.LastPrice} | Open time: {lastCandle.Timestamp} | Close: {lastCandle.Close}");

       if (_orderSide == OrderSide.Sell)
       {
           var trade = _trades.FirstOrDefault(e => e.Pair.Equals(_tradeOptions.Pair) && e.IsOpen);

           if (trade.NotNull())
           {
               var shouldSell = _tradingStrategy.ShouldSell(trade, _ticker.LastPrice, _tradeAdvice);

               if (shouldSell.SellFlag)
               {
                   await ExecuteSellAsync(trade, lastCandle.Timestamp, shouldSell.SellType).ConfigureAwait(false);
               }
           }
       }
   }))
   .Subscribe();

編輯

我知道問題出在哪里。 _tradingStrategy.ShouldSell 需要幾秒鍾來執行,下一次執行同時開始執行下一次檢查。 我可以在該邏輯中使用lock嗎?

這就是解決它的方法,但我需要鎖定整個檢查:

bool test = false;
public (bool SellFlag, SellType SellType) ShouldSell(Trade trade, decimal rate, TradeAdvice tradeAdvice, decimal? low = null, decimal? high = null)
{
    if (!test)
    {
        test = true;

        // my logic is here. It takes a few seconds.

        test = false;
    }

    return (false, SellType.None);
}

編輯2

可測試的代碼。 Observable.Interval每秒執行一次,ShouldSellAsync 的邏輯執行需要 5 秒。 一旦 _completed 變為true ,則不再打印該消息。 它執行消息 5 次,而我只希望它執行一次。

using System;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;

namespace RxNETDispose
{
    class Program
    {
        private static bool _completed = false;

        public static async Task ShouldSellAsync()
        {
            if (!_completed)
            {
                await Task.Delay(5000);

                Console.WriteLine($"{DateTime.UtcNow} - ShouldSell called");

                _completed = true;
            }
        }

        static void Main(string[] args)
        {
            Observable.Interval(TimeSpan.FromSeconds(1))
                .SelectMany(_ => Observable.FromAsync(async () =>
                {
                    await ShouldSellAsync().ConfigureAwait(false);
                }))
            .Subscribe();

            Console.ReadLine();
        }
    }
}

SelectMany確實引入了並發性。 我們希望控制這種並發性,因此這里的答案是滾動您自己的運算符,以確保對ExecuteSellAsync的調用之間存在固定的間隙。

值得慶幸的是,使用 Rx 調度程序有一個漂亮但不明顯的方法來做到這一點。

我們正在尋找的方法是這樣的:

public static IDisposable ScheduleAsync(this IScheduler scheduler, TimeSpan dueTime, Func<IScheduler, CancellationToken, Task<IDisposable>> action)

要使用此調用,需要將Func<IScheduler, CancellationToken, Task<IDisposable>> action定義為遞歸的,以便在對ExecuteSellAsync的調用完成后調用自身重新調度。

因此,要每2.0秒執行一次,例如,我們這樣做:

Func<IScheduler, CancellationToken, Task<IDisposable>> handler = null;
handler = async (s, ct) =>
{
    await ExecuteSellAsync();
    return s.ScheduleAsync(TimeSpan.FromSeconds(2.0), handler);
};

我們可以通過調用它來啟動它:

IDisposable subscription = Scheduler.Default.ScheduleAsync(TimeSpan.Zero, handler);

當然,像所有好的 Rx 操作一樣,我們可以調用subscription.Dispose()來停止它的運行。

這是一個完整的例子:

async Task Main()
{
    Func<IScheduler, CancellationToken, Task<IDisposable>> handler = null;
    handler = async (s, ct) =>
    {
        await ExecuteSellAsync();
        return s.ScheduleAsync(TimeSpan.FromSeconds(2.0), handler);
    };
    
    IDisposable subscription = Scheduler.Default.ScheduleAsync(TimeSpan.Zero, handler);

    await Task.Delay(TimeSpan.FromSeconds(9.0));
    
    subscription.Dispose();
 }

private DateTime then = DateTime.Now;
private int __counter = 0;

async Task ExecuteSellAsync()
{
    var counter = Interlocked.Increment(ref __counter);
    Console.WriteLine($"ExecuteSellAsync() Start {counter} - {DateTime.Now.Subtract(then).TotalSeconds}");
    await Task.Delay(TimeSpan.FromSeconds(2.0));
    Console.WriteLine($"ExecuteSellAsync() End {counter} - {DateTime.Now.Subtract(then).TotalSeconds}");
}

當我運行它時,我得到了這個 output:

ExecuteSellAsync() Start 1 - 0.0019952
ExecuteSellAsync() End 1 - 2.0095866
ExecuteSellAsync() Start 2 - 4.0185182
ExecuteSellAsync() End 2 - 6.0199157
ExecuteSellAsync() Start 3 - 8.0303588
ExecuteSellAsync() End 3 - 10.0417079

請注意, ExecuteSellAsync()不會合作取消,因此它會運行到完成。 不難改成async Task ExecuteSellAsync(CancellationToken ct)讓其協同取消。

現在,可以擴展它以使其成為一個很好的可觀察對象。

嘗試這個:

IObservable<Unit> query =
    Observable.Create<Unit>(o =>
    {
        Func<IScheduler, CancellationToken, Task<IDisposable>> handler = null;
        handler = async (s, ct) =>
        {
            if (ct.IsCancellationRequested)
            {
                o.OnCompleted();
            }
            else
            {
                await ExecuteSellAsync();
                o.OnNext(Unit.Default);
            }
            return s.ScheduleAsync(TimeSpan.FromSeconds(2.0), handler);
        };
    
        return Scheduler.Default.ScheduleAsync(TimeSpan.Zero, handler);
    });

IDisposable subscription = query.Take(3).Subscribe(x => Console.WriteLine("U"), () => Console.WriteLine("C"));

await Task.Delay(TimeSpan.FromSeconds(11.0));

subscription.Dispose();

這有以下 output:

ExecuteSellAsync() Start 1 - 0.0009972
ExecuteSellAsync() End 1 - 2.0115375
U
ExecuteSellAsync() Start 2 - 4.0128375
ExecuteSellAsync() End 2 - 6.0282818
U
ExecuteSellAsync() Start 3 - 8.0370135
ExecuteSellAsync() End 3 - 10.0521106
U
C

注意它完成。 如果你調用subscription.Dispose(); 在它自然完成之前,它會正常運行並且不會發出OnComplete通知。

讓我們將其包裝在一組不錯的擴展方法中:

public static class ObservableEx
{
    public static IObservable<Unit> IntervalAsync(TimeSpan period, Func<Task> actionAsync, IScheduler scheduler) =>
        TimerAsync(period, period, actionAsync, scheduler);

    public static IObservable<T> IntervalAsync<T>(TimeSpan period, Func<Task<T>> functionAsync, IScheduler scheduler) =>
        TimerAsync(period, period, functionAsync, scheduler);

    public static IObservable<Unit> TimerAsync(TimeSpan dueTime, TimeSpan period, Func<Task> actionAsync, IScheduler scheduler) =>
        Observable.Create<Unit>(o =>
        {
            Func<IScheduler, CancellationToken, Task<IDisposable>> handler = null;
            handler = async (s, ct) =>
            {
                if (ct.IsCancellationRequested)
                {
                    o.OnCompleted();
                }
                else
                {
                    await actionAsync();
                    o.OnNext(Unit.Default);
                }
                return s.ScheduleAsync(period, handler);
            };
            return scheduler.ScheduleAsync(dueTime, handler);
        });

    public static IObservable<T> TimerAsync<T>(TimeSpan dueTime, TimeSpan period, Func<Task<T>> functionAsync, IScheduler scheduler) =>
        Observable.Create<T>(o =>
        {
            Func<IScheduler, CancellationToken, Task<IDisposable>> handler = null;
            handler = async (s, ct) =>
            {
                if (ct.IsCancellationRequested)
                {
                    o.OnCompleted();
                }
                else
                {
                    o.OnNext(await functionAsync());
                }
                return s.ScheduleAsync(period, handler);
            };
            return scheduler.ScheduleAsync(dueTime, handler);
        });
}

現在,很明顯有一堆我沒有寫的重載——使用默認調度程序的和允許合作取消的——但我希望你明白這一點。

現在使用這些擴展方法,我可以做到這一點:

IDisposable subscription =
    ObservableEx
        .IntervalAsync(TimeSpan.FromSeconds(2.0), () => ExecuteSellAsync(), Scheduler.Default)
        .Take(3)
        .Subscribe(x => Console.WriteLine("U"), () => Console.WriteLine("C"));

我得到了和以前一樣的 output。

我還沒有完全測試擴展方法。 他們可能需要更多的愛和關注。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM