Rehber C# Hassas bekleme kütüphanesi Winhooks nasıl kullanılır?

Windows'taki popüler bekleme sınıflarını bir araya toplayıp detaylandırdım. Ve o artık public! Bekleme sürelerini HighRes ile mikro saniye sapmalarla kendi uygulamalarınızı en hassas şekilde geliştirmenize yarayan yardımcı bir .NET 9 C# kütüphanesi.

Nasıl kullanılır?​

ReisProduction Adresinden Windelay'i indiriyoruz.
Ben size göstermek amaçlı .NET 9 C# Console Application oluşturuyorum.
Öncelikle bu projenin .NET 9 ve Minimum Windows 19041 sürümü gerektiğini size bildirmek isterim. İsterseniz kendiniz fork atıp eski versiyonlara da uyumlu hâle getirebilirsiniz de zaten eski bilgisayarlara Sleep veya Delay methodları yetecektir. Ne kadar hassas bir bekleme yapacaksınız ki?
1756248067886.png

Windelay kütüphanesini ekliyoruz:
1756248130226.png

Kodu test etme vakti. Aşağıda sapma süreleri ve kodlar bulunuyor:
1756249709199.png

C#:
using ReisProduction.Windelay.Utilities.Enums;
using ReisProduction.Windelay.Utilities;
using System.Diagnostics;
using static ReisProduction.Windelay.Models.DelayExecutor;
internal class Program
{
    private static void Main(string[] __)
    {
        Console.WriteLine("Hello, Techonpat Social!");
        StartTest();
        Console.ReadLine();
    }
    private static async void StartTest()
    {
        Random rnd = new();
        CancellationTokenSource cts = new();
        Console.WriteLine("HybridDelay Test Başladı\n");
        //SpinWaitIterations = 10; // İsterseniz daha hassas yapabilirsiniz fakat gereksiz olur. Zaten 25 çok agresif bir hassaslık.
        // SpinWait için iterasyon sayısı (Varsayılan: İşlemci çekirdeğine göre değişir.)
        // public static int SpinWaitIterations { get; set; } = Math.Clamp(200 / Environment.ProcessorCount, 25, 100);
        //SpinAheadMilisecond = 500; // İsterseniz daha hassas yapabilirsiniz fakat iyi bilgisayarlar için gereksiz olur. Kötü bilgisayarlar için ise max 500ms yeterlidir.
        // HybridDelay için SpinAhead süresi (Varsayılan: 200ms)
        //public static int SpinAheadMilisecond { get; set; } = 200;
        for (int i = 0; i < 20; i++)
        {
            int ms = rnd.Next(200, 2000); // 200ms - 1000ms arası random süre
            DelayAction delay = new
            (
                ms,
                cts.Token, // İptal edebilmek için
                DelayType.HybridDelay // Delay türü (Burada HybridDelay kullanıldı) Sistemi en az yorarak en doğru sonucu verir.
                                      // Ancak DelayType vermek, sadece HandleDelay() methodunda geçerlidir.
            );

            Stopwatch sw = Stopwatch.StartNew();
            //HandleDelay(delay); // DelayType parametresi verilmezse, default olarak HybridDelay kullanılır.
            await HybridDelay(delay); // En sağlıklı ve önerilen yöntem budur.
            sw.Stop();

            double elapsed = sw.Elapsed.TotalMilliseconds,
                      diff = elapsed - ms;

            Console.WriteLine($"Hedef: {ms,4:0000} MS | Beklenen: {elapsed,8:0000.00} MS | Sapma: {diff,8:+0000.00;-0000.00;+0000.00} MS");
        }
        Console.WriteLine("\nTest Bitti.\nHighResSpin Test Başladı");
        for (int i = 0; i < 20; i++)
        {
            int ms = rnd.Next(10, 1000); // 10ms - 1000ms arası random süre
            DelayAction delay = new
            (
                ms,
                cts.Token, // İptal edebilmek için
                DelayType.HybridDelay // Delay türü (Burada HybridDelay kullanıldı) Sistemi en az yorarak en doğru sonucu verir.
                                      // Ancak DelayType vermek, sadece HandleDelay() methodunda geçerlidir.
            );

            Stopwatch sw = Stopwatch.StartNew();
            //HandleDelay(delay); // DelayType parametresi verilmezse, default olarak HybridDelay kullanılır.
            HighResSpin(delay); // En sağlıklı ve önerilen yöntem budur.
            sw.Stop();

            double elapsed = sw.Elapsed.TotalMilliseconds,
                      diff = elapsed - ms;

            Console.WriteLine($"Hedef: {ms,4:0000} MS | Beklenen: {elapsed,8:0000.00} MS | Sapma: {diff,8:+0000.00;-0000.00;+0000.00} MS");
        }
        Console.WriteLine("\nTest Bitti.");
    }
}
Yakında Winhook ve Winjoys'u da public yapmayı düşünüyorum. Belki sonrasında yapacağım Winmacro'yu da ücretsiz olarak public sunabilirim. Ben macro kullanmayı seven birisiyim ve Jitbit Recorder'a bağlı kalmaktan memnun değilim. Daha iyisini ücretsiz bir şekilde yapabileceğimi düşünüyorum. Şu anda gayet iyi gidiyorum. Görünüm olarak da WinUI 3 kullanacağımdan, o konuda problemim olmayacak. Kütüphaneyi inceleyip eklememi istediğiniz özellik veya düzeltmem gereken bir bug bulursanız bildirmeyi unutmayın. Şimdilik bitti ve rafa kaldırdım. Arada bir değişiklik olursa güncellemeler getiririm.

Sırada ise LowLevel Hook ve ardından LowLevel Injector'u tamamen bitirip public yapacağım.

NOT: Güncellemeler pushlanıp v25.5.1 olarak GitHub'a eklenmiştir. NuGet olarak da eklenmiştir.

İyi günler ve iyi çalışmalar dilerim!

Yakında çıkacak Windoc uygulamamı takip etmek için: Bilgisayarın sağlığını ve performansını kontrol eden uygulamam Windoc
 
Son düzenleyen: Moderatör:
Eline saglik guzel kutuphane olmus. Birden fazla secenek eklemen guzel olmus.

Yine de bir kac sey eklemek isterim, ozellikle kutuphaneyi kullanacaklar icin. Senin zaten asagida anlatacagim seyleri bildigini dusunuyorum. Biraz fazla uzun bir metin oldu ama...;

async olmayanlarin geneli cogu senaryoda zararli, sahsi fikrim, ozellikle spin waitler. Hybrid olan guzel.

Cok uzun okumadim diyenler icin kisa ozet (TL;DR):
Asynchronous ortamlarda calisirken blocking callarin tamami diger ayni thread'de calisan diger tasklarin da takilmasina sebep olacak. HighResSpin bu konuda en tehlikelisi, cunku sadece thread'i bloklamiyor, bi de ustune CPU'ya spin attirip kullanimi arttiriyor. 10 tane worker threadin olsa, 10'u da bu spin wait'e gelse, CPU bu durumdan mutlu olmazdi.

1756294552319.gif

ScreenRecording2025-08-27at3.12.06PM-ezgif.com-video-to-gif-converter.gif


Full metin:
Blocking olmayanlarin da 3 tanesi mutli-thread calisiyor, bu ucu de ek thread spawnlamak demek. Blocking olanlara gore kesinlikle daha iyi ama daha iyi alternatifler var beklemek icin.

Sahsen eger cikacak 5-10 ms jitter onemli degilse direkt await Task.Delay(...) der gecerim.

Async bir method olusturdugunda C#'ta, method bir state machine'e donusuyor. Bu state machineler bir awaitle karsilastiklari zaman eger awaiter tamamlanmamissa, fonksiyondan cikarlar (state'i await'in oldugu noktaya kadar kaydeder), cikis yapmadan once awaiter'in eventlerine kendisi icin bir callback ekler, awaiter tamamlandiginda TaskScheduler'a ekler callback ile.

Bunun artisi bir thread'i yada I/O islemini baska bir thread'i bloklamadan bekleyebilmek. Task.Delay dedigin zaman bi DelayPromise olusuyor. Olusan bu promise bir TimerQueueTimer olusturuyor (Windows uzerinde CreateTimerQueueTimer kullanir Win32 API'lerinden). Timerlarin calistirilacagi/takip edilecegi bir thread'e sahip. Tek thread uzerinde birden fazla (belki binlerce) timer'i takip edebilir. Tamamlandiginda DelayPromise'i tamamlar (callback ile). DelayPromise tamamlandiginda awaiter'i tamamlar. Tamamlanan awaiter'da state machine'i yeniden schedule eder.

State machine'de baya bildigin bi ton if else var. Await'e kadar olan kismi bi if else alir, await'i baska bi if else'e, ondan sonra ki blogu baska bir if else eger bi daha await varsa alir. Flag flag dusun, kaldigi yerden devam edebilmek adina tek tek bakiyor, burda mi kalmisim, surda mi kalmisim diye.
C#:
private sealed class <Delay>d__0 : IAsyncStateMachine
    {
        public int <>1__state;

        public AsyncTaskMethodBuilder <>t__builder;

        public int ms;

        [Nullable(0)]
        public Test <>4__this;

        private TaskAwaiter <>u__1;

        private void MoveNext()
        {
            int num = <>1__state;
            try
            {
                TaskAwaiter awaiter;
                if (num != 0)
                {
                    awaiter = Task.Delay(ms).GetAwaiter();
                    if (!awaiter.IsCompleted)
                    {
                        num = (<>1__state = 0);
                        <>u__1 = awaiter;
                        <Delay>d__0 stateMachine = this;
                        <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
                        return;
                    }
                }
                else
                {
                    awaiter = <>u__1;
                    <>u__1 = default(TaskAwaiter);
                    num = (<>1__state = -1);
                }
                awaiter.GetResult();
            }
            catch (Exception exception)
            {
                <>1__state = -2;
                <>t__builder.SetException(exception);
                return;
            }
            <>1__state = -2;
            <>t__builder.SetResult();
        }

        void IAsyncStateMachine.MoveNext()
        {
            //ILSpy generated this explicit interface implementation from .override directive in MoveNext
            this.MoveNext();
        }

        [DebuggerHidden]
        private void SetStateMachine(IAsyncStateMachine stateMachine)
        {
        }

        void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
        {
            //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
            this.SetStateMachine(stateMachine);
        }
    }

Simdi bu kadar seyi anlattigimiza gore spinwaitlerin problemine gelelim. Yukaridaki kodu incelediginde (sadece delay yapiyor) MoveNext'in await'e geldiginde AwaitUnsafeOnCompleted diye bir method ile callback yaptigini ve sonrasinda return yaptigini goreceksin. Daha sonrasinda Await tamamlandiginda bu MoveNext tekrar calisacak, ama state 0 oldugu icin Task.Delay tekrar calismak yerine, awaiter'inin sonucunu isteyecek (else kismi).

Ancak spin waiti incelersek (kasten async bir method icerisine yazdim ki, ayni contextte degerlendirebilelim);
C#:
private sealed class <Spin>d__5 : IAsyncStateMachine
    {
        public int <>1__state;

        public AsyncTaskMethodBuilder <>t__builder;

        public int ms;

        [Nullable(0)]
        public Test <>4__this;

        private long <freq>5__1;

        private long <target>5__2;

        private void MoveNext()
        {
            int num = <>1__state;
            try
            {
                if (ms > 0)
                {
                    <freq>5__1 = Stopwatch.Frequency;
                    <target>5__2 = Stopwatch.GetTimestamp() + ms * <freq>5__1 / 1000;
                    while (Stopwatch.GetTimestamp() < <target>5__2)
                    {
                        Thread.SpinWait(SpinWaitIterations);
                    }
                }
            }
            catch (Exception exception)
            {
                <>1__state = -2;
                <>t__builder.SetException(exception);
                return;
            }
            <>1__state = -2;
            <>t__builder.SetResult();
        }

        void IAsyncStateMachine.MoveNext()
        {
            //ILSpy generated this explicit interface implementation from .override directive in MoveNext
            this.MoveNext();
        }

        [DebuggerHidden]
        private void SetStateMachine(IAsyncStateMachine stateMachine)
        {
        }

        void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
        {
            //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
            this.SetStateMachine(stateMachine);
        }
    }

Cagrildigi yerde belirlenen sure boyunca tikayacak. Bu da 10 thread'in ve task'in varsa, 10 tanesi de bekliyorsa, bunlar bekledigi sure boyunca program hic bir sey yapmayacak, hatta bi de islemci kaynaklarini bosa tuketecek demek. Delay kullandigin zaman 1 thread'in ve 10 task'in varsa, 10 task'da beklese, o tek thread tek basina diger isleri yurutmeye devam edebilir.

Eger thread'in bloklanmasi bir problem degilse, full spin wait yerine, thread'i uykuya alirsin, uyku cikisi spin wait atip tamamlarsin. En azindan sistem kaynaklari bosa gitmemis olur.

Yukaridaki GIF'i kendi bilgisayarinizda test etmek isterseniz;
C#:
using System.Diagnostics;

static async Task Test(int threads, int ms)
{
    threads = Math.Max(1, threads);

    Console.WriteLine("---- SpinWait ----");
    await Run(threads, () => Task.Run(() => SpinWait(ms)));

    Console.WriteLine("---- Delay ----");
    await Run(threads, () => Task.Delay(ms));
}

static async Task Run(int n, Func<Task> op)
{
    var p = Process.GetCurrentProcess();
    var cpu0 = p.TotalProcessorTime;
    var t0 = Stopwatch.GetTimestamp();

    var tasks = Enumerable.Range(0, n).Select(_ => op()).ToArray();
    await Task.WhenAll(tasks);

    var cpu1 = p.TotalProcessorTime;
    var dt = (Stopwatch.GetTimestamp() - t0) / (double)Stopwatch.Frequency;
    var cpuMs = (cpu1 - cpu0).TotalMilliseconds;
    var wallMs = dt * 1000 * Environment.ProcessorCount;
    var usage = cpuMs / wallMs * 100.0;
    Console.WriteLine($"> Threads={n} | ProcCPU={usage:F1}%");
}

/*
 * HighResSpinWait'in bu kisim icin uyarlanmis hali.
 */
static void SpinWait(int ms)
{
    if (ms <= 0) return;
    long freq = Stopwatch.Frequency;
    long target = Stopwatch.GetTimestamp() + (long)(ms * (freq/1000.0));
    int iters = Math.Clamp(200 / Environment.ProcessorCount, 25, 100);
    while (Stopwatch.GetTimestamp() < target)
        Thread.SpinWait(iters);
}

await Test(Environment.ProcessorCount / 2, 3000);
 
Son düzenleme:
@TheAny Öncelikle teşekkür ederim. Zaten amacı ilgili threadı bekletmek. CTS ile bunu farklı threadden durdurabilir. Ek olarak await yerine GetAwaiter veya Wait de bu konuda hassas çalışıyor. Tabii sizin bahsettiğiniz ex ile hiç karşılaşmadım. İlk defa dll yaptığımdan bilmiyorum. Bir de HighRes CPU neredeyse hiç yormaz aslında. Yorsa bile bu benim sorunum değil. Çok ufak mslerdeyken kullanılmalı. Örneğin 10MS var bunu Sleep veya Delay asla yapamaz. Zaten OS schuduler en az 15ms bekliyor. Ben de HybridDelay da 200ms altında hemen çağırıyorum. Şahsen benim işlemci ortalama bir işlemci 8840Hs ve 5700x ikisi de 200ms de Spinahead 25 oluyor ve cpuyu %5 bile arttırmıyor diyebilirim.

Eğer dediklerinizi doğru anladıysam bunlar aslında kütüphanenin asıl amacı. Şahsen Task veya Thread sınıfının beklemeleri, bana cazip gelmiyor. Zaten başta onlarla yaptım fakat hiçbiri testlerde başarılı çıkmadığından Spin methodunu öğrendim. Sonra da yüksek mslerde işlemciye yük oluyormuş derken kendimi bu sınıfı yaparken buldum.

Bir de ben Web geliştirmiyorum. Senk olması bir problem değil. Şu anda bir makro yazılımı yapmaya çalışıyorum. JitBitten hızlı ve WinUI 3 teknolojisiyle. Bildiğim kadarıyla o Send input kullanmıyor ama ben o ve WinUI 3 ve UWP ye özel InputInjector kullanacağım. Kernel level veya direkt driver olmadığından onun kadar kusursuz olur mu bilmiyorum ama şu an gayet de güzel gidiyor.

Tek problem Lowlevel hook ve Input sınıfının System.VirtualKey sınıfından olması ve Türkçe karakterleri desteklememesi. Henüz bakmadım ama illa bir yöntemi vardır.

Şu an düzenleme ekledim de. Özette 10 Thread ve Await değilse diye okudum. Bu bence benim değil geliştiricinin sorunu değil midir? Bir CTS ile 11. Thread'de yönetebilir diye düşünüyorum. Kaldı ki niye 10 Thread'de yüksek ms de bekletsin.
Bilmiyorum. Anlayamadıysam kusura bakmayın.

Şimdi tekrardan test ettim de. Arkada birçok işlem var ve aynı testi bir daha başlattım. Build ettim ve 10 saniye sonra test başlaması için Delay ekledim. Test başladıktan sonraki değerler bu şekilde: Ek olarak Hook gibi performans düşürecek şeyler de açıktı.
1756302074940.png

Sizin işlemci EPYC'di sanırsam. O işlemcinin çok yüksek bir Iterations'a ihtiyacı yok ki. Zaten çok hızlı spin atar. Onun ayarını kendiniz tutturmalısınız. Benim işlemcim ve ortalama için 25 ve 100 arası gayet mâkul. Sizinki günlük kullanım işlemcisi olmadığından spin değerine 25 vermek saniyede yüzlerce spini geçer diye düşünüyorum. Bundan dolayı 50 bile 0 ms sapmayla süreyi verebilir.

Sahsen eger cikacak 5-10 ms jitter onemli degilse direkt await Task.Delay(...) der gecerim.
Zaten her uygulama için bu kesinlikle mantıklı. Bu kütüphaneyi kullanmak için kesinlikle hassas işlemler olmalı. Örneğin şu anda yapacağım macro yazılımı için. Ben aynı zamanda oyun oynayan birisiyim. Oynadığım oyunda bir işlemi çok hızlı yapmam lazım ki aynı zamanda bu sürelerin çakışmaması lazım. Yani o fazladan kaybettiğim 10MS aşağısı veya yukarısı benim için dezavantaj. Tabii ki bu herkes için geçerli değildir veya dünyada sayılı projelerde kullanılır.

Sıradan bir proje için bu kütüphaneyi kullanmak çok gereksiz bağımlılık olur. İçine Forms bile ekledim ki ne alaka ben de bilmiyorum :D

Bir iş yaparken her şeyin ayrıntılarına girmeyi/en iyisini yapmaya çalışmayı severim.

Özetle:

Uyumasından veya başka işlere bırakmasındansa spin atması sonucu çok keskin yapıyor. Hybrid delay da tam da bu ikisinin ortasına geliyor. Delay methodu çok ama çok nadir 200MS'üstü sapar. Onun dışında genelde 10-20MS saptığından 200MS'e gelince kendini spin'e devrediyor ki keskin bir sonuç sağlasın. Bu da işlemciyi neredeyse 0 yorarak 0 sapmaya ulaşabilmemize olanak sağlıyor. Mikro saniyelerin hiçbir önemi yok. Kaldı ki mikro saniyelerle StopWatch, HighRes dışında işlem yapabilecek bir kütüphane/donanım yoktur. Çok uzun sürelerde asla Spin kullanılmamalı. Normal projelerde de bu kütüphane kullanılmamalı. Aynı önceki rehberimde olduğu gibi:
Bu mesela. Eğer dışarıdan sadece Uygulamayı kapat, Mesaj gönder vb. gibi şeyler yapılacaksa bir iki helper method ile daha basit bir şekilde yapılır.
 
Son düzenleme:
Cok kisa sureler icin spin bi sorun degil. 10 ms bi sey degil. 200 ms bi sey.

Ancak atiyorum, count down yapiliyordur, bi cesit senkronizasyon amaci vardir (bad practice ama yine de) vs uzun beklemelerde spin CPU source kullanir, bosa harcar. Sadece web istekleri icin degil, keyboard dinlemelerinde de gerek yok spin'e. Kaynak bol diye bosa harcamak mantikli degil.

Eger lock'lari takip edip en kisa surede yakalamaksa amac, spin wait guzel ve tatli.

10 thread oldu denk geldi basit bir ornek. Benim testi yapip attigim cihaz Macbook M4 Pro. Spin wait tight loop calistirir. Tight looplar interrupt edilmedikleri muddetce CPU zamanini tamamen alirlar. Her biri bir cekirdegi suistimal eder.

Simdi mevzuyu soyle dusun, programinda arka arkaya tetiklenen bir suru bi sey var, bi noktada 200 ms bekledigi bi noktaya geliyor. Bu 200 ms'i beklerken yeni task postladiginda ThreadPool bakiyor, eldeki thread mesgul, yeni thread spawnliyor. Spawnladigi thread'de ayni yere geldi bekledi, bu sirada yeni bi task geldi o da ayni yere geldi. Burda uc durum var, eger ilk ikisinden biri bittiyse, 4. thread dogmak yerine 1 veya 2 yeniden kullanilir. Olurda race olursa 5 spawnlanir.

Daha rahat anlasilmasi icin ornek;
Task 1 arrived -> Spawn Thread (1) -> Do something -> Spin Wait (200 ms) -> after 10 ms ->
Task 2 arrived -> Thread 1 (Unavailable - 190 ms left) -> Spawn Thread (2) -> Do Something -> Spin Wait (200 ms) -> after 15 ms -> Task 3 arrived -> Thread 1 (Unavailable - 175 ms left) -> Thread 2 (Unavailable - 185 ms left) -> Spawn Thread (3) -> ...

Her 10-15 ms'te bir yeni task geldigini, her taskinda bi noktada 200 ms bekledigini dusun. (Kullanici spamliyor, arka arkaya ayni event geliyor vs. fark etmez, herhangi bi sebepten olabilir.)
Bu ilk task bitene kadar 20 thread spawnlaman anlamina geliyor. Eger olurda bu bekleme suresi uzarsa (sonucta bekledikten sonra hala bi seyler yapabilirsin), bu daha fazla thread demek, daha fazla CPU kullanimi demek.

Anlatmaya calistigim sey bu. Sonra kaynak somurusu olur senin islemin icin.
Şimdi tekrardan test ettim de. Arkada birçok işlem var ve aynı testi bir daha başlattım. Build ettim ve 10 saniye sonra test başlaması için Delay ekledim. Test başladıktan sonraki değerler bu şekilde: Ek olarak Hook gibi performans düşürecek şeyler de açıktı.
Process seviyesinde CPU kullanimina bak. Sistem degil.
 
Simdi mevzuyu soyle dusun, programinda arka arkaya tetiklenen bir suru bi sey var, bi noktada 200 ms bekledigi bi noktaya geliyor. Bu 200 ms'i beklerken yeni task postladiginda ThreadPool bakiyor, eldeki thread mesgul, yeni thread spawnliyor. Spawnladigi thread'de ayni yere geldi bekledi, bu sirada yeni bi task geldi o da ayni yere geldi. Burda uc durum var, eger ilk ikisinden biri bittiyse, 4. thread dogmak yerine 1 veya 2 yeniden kullanilir. Olurda race olursa 5 spawnlanir.

Daha rahat anlasilmasi icin ornek;
Task 1 arrived -> Spawn Thread (1) -> Do something -> Spin Wait (200 ms) -> after 10 ms ->
Task 2 arrived -> Thread 1 (Unavailable - 190 ms left) -> Spawn Thread (2) -> Do Something -> Spin Wait (200 ms) -> after 15 ms -> Task 3 arrived -> Thread 1 (Unavailable - 175 ms left) -> Thread 2 (Unavailable - 185 ms left) -> Spawn Thread (3) -> ...

Her 10-15 ms'te bir yeni task geldigini, her taskinda bi noktada 200 ms bekledigini dusun. (Kullanici spamliyor, arka arkaya ayni event geliyor vs. fark etmez, herhangi bi sebepten olabilir.)
Bu ilk task bitene kadar 20 thread spawnlaman anlamina geliyor. Eger olurda bu bekleme suresi uzarsa (sonucta bekledikten sonra hala bi seyler yapabilirsin), bu daha fazla thread demek, daha fazla CPU kullanimi demek.
Şimdi tam anlamıyla anladım. Bu yine benim sorunum sayılmaz. Hassas yapabilmesi için uyutmamalı veya başkasına devretmemeli. Eğer zaten başkasına devrederse ilgili thread'ı bu seferde yapılan işleme göre ciddi sapmalar oluşabilir. Eğer bu sapmalar sizin için önemli değilse ki makro yapmayacaksınız kesinlikle önemi olmaz.

Lowlevel hook için bekleme kullanmıyorum. Sadece MouseHold() methodu var. Mouse Down ve up arasında sürekli tetiklenen bir event için. Onun için de HybridDelay kullanıyorum ve saniyede sadece 5 kere tetikleniyor. Bunu isteğine bağlı azaltıp arttırabiliyor.
Eğer problem hâlâ cpu kullanımı çok artmasıysa
C#:
public static int SpinWaitIterations { get; set; } = Math.Clamp(200 / Environment.ProcessorCount, 25, 100);
public static int SpinAheadMilisecond { get; set; } = 200;
Kullanıcıya bunun gibi fırsat sunmuşum. İstediği kadar tölere ettirebilir.
Bu hassas delayları kullandığım tek yer Makro yazılımında. Yani Inject kısmında. Bu sırada bilgisayarda başka hiçbir şey yapılmayacağından sistemi yormaz. Aynı zamanda makro yazılımında kullanılacak delay tipini de soracağım. Eğer istemezse direkt Delay methodunu bile kullanabilir.

Eğer 0 ms sapma ile daha pratik yöntem varsa bilmiyorum. Çünkü OS timer bile 10-15MS aralıklarla çalışıyor.
 
Son düzenleme:
Hybrid icin soyle demistim. Hybrid olani begenme sebebim spin wait suresini olabildigince minimize ediyorsun.

Diger dedigini eve gecince deneyeyim. Salt spin benim problematik buldugum.
Salt spin'i kullananın bunları düşünmesi gerekir. Benim değil.
Zaten HandleDelay() ile kullanıcıya hiçbir yük bırakmıyorum. Her şeyi içinde hallediyor.
Bunu kullanıcıya özel olarak sunmamın sebebi eğer sadece <200MS çalışıyorsa direkt Spin'i rahatlıkla çağırsın diye. Boşuna branchlerden geçmesin vs. diye.
Eğer kaynağa önem veriyosa orda sistemin %1'ini bile kullanmayacak kadar düşük gereksinimli timer'lar var. Adını bile ilk defa duyduğum ama yine de eklediğim.

Şahsen birinin, Spin'in nasıl kullanıldığını bilmeden; önerilen olan HybridDelay() methodu yerine kullanacağını sanmıyorum. Kaldı ki yine aynı kapıya geliyoruz. Kullanırsa, bu onun sorunu; benim değil.

Benim öğrendiğim şekilde böyle yani.
Mesela Task.Delay Token verirsek exception fırlatırsa yakalaması bize düşer sınıfa değil. Aynı zamanda Spin'in 200MS üzerinde çalıştırılmayacağını kullanan kişi bilmeli.

Benim düşüncem bu yönde. Yanlışım varsa düzeltin.
Eğer 0 ms sapma ile daha pratik yöntem varsa bilmiyorum. Çünkü OS timer bile 10-15MS aralıklarla çalışıyor.

Process seviyesinde CPU kullanimina bak. Sistem degil.
Sağdaki değer de Process seviyesinde değil mi? Bilmediğimden soruyorum.
 
Sağdaki değer de Process seviyesinde değil mi? Bilmediğimden soruyorum.
Evet, oyle. Dikkat etmemisim. Tek thread'de spin wait denediysen yada hybrid wait denediysen islemcin ondan yukselmemistir. Test koduyla da deneyebilirsin.
Yukaridaki GIF'i kendi bilgisayarinizda test etmek isterseniz;


Şahsen birinin, Spin'in nasıl kullanıldığını bilmeden; önerilen olan HybridDelay() methodu yerine kullanacağını sanmıyorum. Kaldı ki yine aynı kapıya geliyoruz. Kullanırsa, bu onun sorunu; benim değil.
Tabii ki senin degil. O yuzden zaten;
Yine de bir kac sey eklemek isterim, ozellikle kutuphaneyi kullanacaklar icin. Senin zaten asagida anlatacagim seyleri bildigini dusunuyorum.
Demistim.

Daha saglikli bir secim yapmalari icin verilen wait turleri arasinda yazdim mesaji aslinda diyebiliriz.
 
Evet, oyle. Dikkat etmemisim. Tek thread'de spin wait denediysen yada hybrid wait denediysen islemcin ondan yukselmemistir. Test koduyla da deneyebilirsin.
Hybrid ardından Spin denedim. Fakat UI + Hook + Spin 3ü de main(ui) thread'da olduğunda arada mouse çok hızlı çevirdiğimde donma yaratıyor. Bu da lowlevel Mousehook ile ilgili. Çünkü pixel başına izliyor ve saniyede binlerce method yanında binlerce de switch ve branchler karşılıyor. Doğal olarak mainthread'ı blockluyor. Ama aynı thread'de olmamalı zaten. Ek olarak bu sistemi yoran her şey için prop verdim. Yâni eğer isterse 100MS'den kısa süren Move işlemlerini görmezden gel diyebiliriz. Veya 100px'den az bir move yaşanırsa onu da görmezden gel diyebiliriz.

Spin ile tekrar deneyeyim.
1756305470492.png

20 test <1000MS. 0 MS sapma ve %3 kullanım falan arttı sanırım.
Ek olarak önceki mesajlarımda da demiştim. İşlemci ne kadar güçlü olursa Spin o kadar artacağı için aynı Iteration değerine sahip olsak bile sizinki daha ciddi döneceği için artma çok normal.
C#:
public static int SpinWaitIterations { get; set; } = Math.Clamp(200 / Environment.ProcessorCount, 25, 100);
public static int SpinAheadMilisecond { get; set; } = 200;
Bu iki prop ile işlemcinizi daha az yorup sapmamayı koruyabilirsiniz diye düşünüyorum.
Şimdi de 5 tane birden çalıştırdım. Yani 5*20 Test oluyor. <1000MS paralel for
1756307230886.png

Tabii ki senin degil. O yuzden zaten;
Demistim.
O zaman bir problem yok. Bu kütüphaneyi kullanacak kişinin bu bilgileri bilmesi gerekirdir herhalde.

Daha saglikli bir secim yapmalari icin verilen wait turleri arasinda yazdim mesaji aslinda diyebiliriz.
Bunu benden başka kaç kişi kullanır o bile belli değil. Belki test eden ilk ve son kişisinizdir o bile belli değil. Yapacağım makro uygulamasında bunların hepsini seçtirme imkanı sunacağım. Enum listesine göre bir liste olacak:
C#:
namespace ReisProduction.Windelay.Utilities.Enums;
public enum DelayType : ushort
{
    HybridDelay = 0x01,
    HighResSpin = 0x02,
    SpinDelay = 0x03,
    WaitableTimer = 0x04,
    HighResSleep = 0x05,
    SleepDelay = 0x06,
    TaskDelay = 0x07,
    TaskDelayWait = 0x08,
    EventWaitHandle = 0x09,
    TimersTimerDelay = 0x0A,
    TimerDelay = 0x0B,
    FormsTimerDelay = 0x0C
}
Yüksek güç tüketip daha hassas'dan başlayıp azalan şekilde. İlk sırada hybrid var. Diğerlerini seçerken uyarıda bulunurum.

Ek olarak makronun herhangi bölümünde bu spin değerini arttırıp azaltma imkanı da sunacağım. Paralel makro kullanmasına da olanak sağlayacağım ancak dediğiniz gibi 10 makro -> high res yaparsa bu en iyi ihtimalle crash vermesine sebep olabilir. Asıl problem bu da değil. Kullandığım 20 yıl önceki teknoloji SendInput apisini çok fazla çağırmak OS Thread'inde çökmeye neden oluyor. Bu da doğal olarak mavi ekrana veya sistemi yeniden başlatmaya sebep oluyor.

O kadar da kaşınmamak gerekir.

SendInput apisini çökertmemin sebebi ise Keyleri tek tek yolluyordum normalde. Yani X Keydon Y KeyUp ve Z Keypress olursa (aralarında bekleme yok) hepsi için ayrı ayrı api call yapıyordum bu da OS thread'ında sıkışmaya ve crash'e neden olabiliyordu. Bunun önüne ciddi şekilde geçmek için de aralarında wait olmayan injectleri tek seferde call ediyorum. Bunun sayesinde %80 oranında crash azalır.
 
Son düzenleme:

Technopat Haberler

Yeni konular

Geri
Yukarı