///<summary>これを定期的に呼んで再接続やFriendの取得をやらせる</summary>
        public async ValueTask <int> ConnectStreamers()
        {
            if (!await db.ExistThisPid().ConfigureAwait(false))
            {
                Environment.Exit(1);
            }

            int ActiveStreamers = 0;  //再接続が不要だったやつの数
            ActionBlock <UserStreamer> ConnectBlock = new ActionBlock <UserStreamer>(
                async(Streamer) =>
            {
                try
                {
                    UserStreamer.NeedConnectResult NeedConnect = Streamer.NeedConnect();
                    //初回とRevoke疑いのときだけVerifyCredentials()する
                    //プロフィールを取得したい
                    if (NeedConnect == UserStreamer.NeedConnectResult.Postponed)
                    {
                        return;
                    }
                    else if (NeedConnect == UserStreamer.NeedConnectResult.First)
                    {
                        switch (await Streamer.VerifyCredentials().ConfigureAwait(false))
                        {
                        case UserStreamer.TokenStatus.Locked:
                            Streamer.PostponeConnect(); return;

                        case UserStreamer.TokenStatus.Revoked:
                            await RevokeRetry(Streamer).ConfigureAwait(false); return;

                        case UserStreamer.TokenStatus.Failure:
                            return;

                        case UserStreamer.TokenStatus.Success:
                            RevokeRetryUserID.TryRemove(Streamer.Token.UserId, out byte gomi);
                            NeedConnect = UserStreamer.NeedConnectResult.JustNeeded;        //無理矢理接続処理に突っ込む #ウンコード
                            break;
                        }
                    }

                    //Streamに接続したりRESTだけにしたり
                    if (NeedConnect == UserStreamer.NeedConnectResult.StreamConnected)
                    {
                        if (Streamer.NeedStreamSpeed() == UserStreamer.NeedStreamResult.RestOnly)
                        {
                            Streamer.DisconnectStream(); return;
                        }
                        else
                        {
                            Interlocked.Increment(ref ActiveStreamers);
                        }
                    }
                    else
                    {
                        //TLの速度を測定して必要ならStreamに接続
                        switch (await Streamer.RecieveRestTimelineAuto().ConfigureAwait(false))
                        {
                        case UserStreamer.TokenStatus.Locked:
                            Streamer.PostponeConnect(); break;

                        case UserStreamer.TokenStatus.Revoked:
                            await RevokeRetry(Streamer).ConfigureAwait(false); break;

                        default:
                            UserStreamer.NeedStreamResult NeedStream = Streamer.NeedStreamSpeed();
                            if (NeedStream == UserStreamer.NeedStreamResult.Stream)
                            {
                                Streamer.RecieveStream(); Interlocked.Increment(ref ActiveStreamers);
                            }
                            //DBが求めていればToken読み込み直後だけ自分のツイートも取得(初回サインイン狙い
                            if (Streamer.NeedRestMyTweet)
                            {
                                Streamer.NeedRestMyTweet = false;
                                await Streamer.RestMyTweet().ConfigureAwait(false);
                                //User streamに繋がない場合はこっちでフォローを取得する必要がある
                                if (NeedStream != UserStreamer.NeedStreamResult.Stream)
                                {
                                    await Streamer.RestFriend().ConfigureAwait(false);
                                }
                                await Streamer.RestBlock().ConfigureAwait(false);
                                await db.StoreRestDonetoken(Streamer.Token.UserId).ConfigureAwait(false);
                            }
                            break;
                        }
                    }
                }
                catch (Exception e) { Console.WriteLine("ConnectBlock Faulted: {0}", e); }
                finally { Streamer.ConnectWaiting = false; }
            }, new ExecutionDataflowBlockOptions()
            {
                MaxDegreeOfParallelism        = config.crawl.ReconnectThreads,
                BoundedCapacity               = config.crawl.ReconnectThreads << 1, //これでもawaitする
                    SingleProducerConstrained = true,
            });

            SetMaxConnections(0);

            Stopwatch sw = new Stopwatch();

            sw.Start();
            await ShowCount();

            foreach (KeyValuePair <long, UserStreamer> s in Streamers.ToArray())  //ここでスナップショットを作る
            {
                if (!s.Value.ConnectWaiting)
                {
                    s.Value.ConnectWaiting = true;
                    await ConnectBlock.SendAsync(s.Value).ConfigureAwait(false);
                }
                do
                {
                    SetMaxConnections(ActiveStreamers);
                    if (sw.ElapsedMilliseconds > 60000)
                    {   //ここでGCする #ウンコード
                        sw.Restart();
                        await ShowCount();

                        //GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; //これは毎回必要らしい
                        //GC.Collect();
                    }
                    //ツイートが詰まってたら休む
                    if (UserStreamerStatic.NeedConnectPostpone())
                    {
                        await Task.Delay(1000).ConfigureAwait(false);
                    }
                } while (UserStreamerStatic.NeedConnectPostpone());
            }
            ConnectBlock.Complete();
            await ConnectBlock.Completion.ConfigureAwait(false);

            return(ActiveStreamers);

            //カウンターを表示したりいろいろ
            async Task ShowCount()
            {
                Counter.PrintReset();
                await WatchDogUdp.SendAsync(BitConverter.GetBytes(ThisPid), sizeof(int), WatchDogEndPoint);
            }
        }
        /// <summary>
        /// これを定期的に呼んで再接続やFriendの取得をやらせる
        /// 1分経過しても終わらないときは打ち切る
        /// 一度も取得していないアカウント→取得待ちが多い順 に処理する
        /// </summary>
        public async Task ConnectStreamers()
        {
            //アカウントが1個も割り当てられなくなってたら自殺する
            if (!await db.ExistThisPid().ConfigureAwait(false))
            {
                Environment.Exit(1);
            }

            var LastCrawlTime = new Dictionary <long, long>();

            while (ConnectBlockProceeeded.TryTake(out var proceeded))
            {
                //ツイートの取得時刻を保存する(たぶん)
                //ここで(前回のConnectStreamers()で)REST取得したやつとUser streamに接続済みのやつだけ処理する(もうないけど)
                if (proceeded.LastReceivedTweetId != 0 || proceeded.NeedConnect() == UserStreamer.NeedConnectResult.StreamConnected)
                {
                    LastCrawlTime[proceeded.Token.UserId] = proceeded.LastMessageTime.ToUnixTimeSeconds();
                }
            }
            var StoreLastCrawlTimeTask = db.StoreCrawlInfo_Timeline(LastCrawlTime);

            Counter.PrintReset();

            //TLがある程度進んでいるアカウントと一定時間取得してないアカウントだけ接続する
            var now = DateTimeOffset.UtcNow;
            var StreamersSelected = Streamers.Select(s => s.Value)
                                    .Where(s => s.EstimatedTweetToReceive >= config.crawl.StreamSpeedTweets ||
                                           (now - s.LastMessageTime).TotalSeconds > config.crawl.MaxRestInterval)
                                    .OrderByDescending(s => s.EstimatedTweetToReceive)
                                    .ThenBy(s => s.LastMessageTime)
                                    .ToArray();

            // UserStreamerの再接続やRESTによるツイート取得などを行うやつ
            var ConnectBlock = new ActionBlock <UserStreamer>(async(Streamer) =>
            {
                try
                {
                    var NeedConnect = Streamer.NeedConnect();
                    //VerifyCredentialsを実行して成功したらtrue
                    bool RevokeCheckSuccess = false;
                    //初回とRevoke疑いのときだけVerifyCredentials()する
                    //プロフィールを取得したい
                    if (NeedConnect == UserStreamer.NeedConnectResult.Postponed)
                    {
                        return;
                    }
                    else if (NeedConnect == UserStreamer.NeedConnectResult.First || RevokeRetryUserID.ContainsKey(Streamer.Token.UserId))
                    {
                        switch (await Streamer.VerifyCredentials().ConfigureAwait(false))
                        {
                        case UserStreamer.TokenStatus.Locked:
                            Streamer.PostponeConnect(); return;

                        case UserStreamer.TokenStatus.Revoked:
                            MarkRevoked(Streamer); return;

                        case UserStreamer.TokenStatus.Failure:
                            return;

                        case UserStreamer.TokenStatus.Success:
                            RevokeCheckSuccess = true;
                            break;
                        }
                    }

                    //Streamに接続したりRESTだけにしたり
                    if (NeedConnect == UserStreamer.NeedConnectResult.StreamConnected)
                    {
                        if (Streamer.NeedStreamSpeed() == UserStreamer.NeedStreamResult.RestOnly)
                        {
                            Streamer.DisconnectStream(); return;
                        }
                        else
                        {
                            Counter.ActiveStreamers.Increment();
                        }
                    }
                    else
                    {
                        //TLの速度を測定して必要ならStreamに接続
                        Counter.RestedStreamers.Increment();
                        switch (await Streamer.RestTimeline().ConfigureAwait(false))
                        {
                        case UserStreamer.TokenStatus.Revoked:
                            //長期間ログインされていないアカウントはVerifyCredentialsしか通らなくなるっぽい
                            //またVerifyCredentialsに失敗するまで放っとく
                            if (RevokeCheckSuccess)
                            {
                                Streamer.PostponeConnect();
                            }
                            //普通のRevoke疑い
                            else
                            {
                                MarkRevoked(Streamer);
                            }
                            break;

                        case UserStreamer.TokenStatus.Locked:
                            if (RevokeCheckSuccess)
                            {
                                UnmarkRevoked(Streamer);
                            }
                            Streamer.PostponeConnect();
                            break;

                        default:
                            if (RevokeCheckSuccess)
                            {
                                UnmarkRevoked(Streamer);
                            }
                            UserStreamer.NeedStreamResult NeedStream = Streamer.NeedStreamSpeed();
                            if (NeedStream == UserStreamer.NeedStreamResult.Stream)
                            {
                                Streamer.RecieveStream(); Counter.ActiveStreamers.Increment();
                            }
                            //DBが求めていればToken読み込み直後だけ自分のツイートも取得(初回サインイン狙い
                            if (Streamer.NeedRestMyTweet)
                            {
                                await Streamer.RestMyTweet().ConfigureAwait(false);
                                //User streamに繋がない場合はこっちでフォローを取得する必要がある
                                if (NeedStream != UserStreamer.NeedStreamResult.Stream)
                                {
                                    await Streamer.RestFriend().ConfigureAwait(false);
                                }
                                await Streamer.RestBlock().ConfigureAwait(false);
                                Streamer.NeedRestMyTweet = false;
                            }
                            break;
                        }
                    }
                    ConnectBlockProceeeded.Add(Streamer);
                }
                catch (Exception e) { Console.WriteLine("ConnectBlock Faulted: {0}", e); }
            }, new ExecutionDataflowBlockOptions()
            {
                MaxDegreeOfParallelism    = ConnectBlockConcurrency,
                BoundedCapacity           = ConnectBlockConcurrency + 1, //これでもawaitする
                SingleProducerConstrained = true,
            });

            var  connectStopWatch = Stopwatch.StartNew();
            bool connectTimedout  = false;

            using (var cancel = new CancellationTokenSource(60000))
            {
                try
                {
                    foreach (var s in StreamersSelected)
                    {
                        if (!await ConnectBlock.SendAsync(s, cancel.Token).ConfigureAwait(false))
                        {
                            break;
                        }                                                                                       //そんなバナナ

                        //ツイートが詰まってたら休む
                        while (UserStreamerStatic.NeedConnectPostpone() && !cancel.Token.IsCancellationRequested)
                        {
                            await Task.Delay(1000, cancel.Token).ConfigureAwait(false);
                        }
                    }
                    ConnectBlock.Complete();
                    await ConnectBlock.Completion.ConfigureAwait(false);
                }
                catch (TaskCanceledException)
                {
                    ConnectBlock.Complete();
                    connectTimedout = true;
                }
            }

            //ConnectBlockの同時接続数を調整する
            connectStopWatch.Stop();
            int oldConnectBlockConcurrency = ConnectBlockConcurrency;

            if (connectTimedout)
            {
                ConnectBlockConcurrency = config.crawl.MaxReconnectThreads;
            }
            else if (connectStopWatch.ElapsedMilliseconds < 40000)
            {
                ConnectBlockConcurrency = Math.Max(1, ConnectBlockConcurrency - 1);
            }
            else if (50000 < connectStopWatch.ElapsedMilliseconds)
            {
                ConnectBlockConcurrency = Math.Clamp((int)Math.Ceiling(ConnectBlockConcurrency * (connectStopWatch.ElapsedMilliseconds / 50000d)), 1, config.crawl.MaxReconnectThreads);
            }
            if (oldConnectBlockConcurrency != ConnectBlockConcurrency)
            {
                Console.WriteLine("ConnectBlockConcurrency: {0} -> {1}", oldConnectBlockConcurrency, ConnectBlockConcurrency);
            }

            await StoreLastCrawlTimeTask.ConfigureAwait(false);

            //Revoke後再試行にも失敗したTokenはここで消す
            await RemoveRevokedTokens().ConfigureAwait(false);

            //接続が全然進まないときは殺してもらう
            if (0 < Counter.ActiveStreamers.Get() + Counter.RestedStreamers.Get())
            {
                await WatchDogUdp.SendAsync(BitConverter.GetBytes(ThisPid), sizeof(int), WatchDogEndPoint).ConfigureAwait(false);
            }

            UserStreamerStatic.ShowCount();
        }