Ayakta Kalan Bir .NET Worker Service’te CAPTCHA Nasıl Çözülür

worker service captcha - How to Solve CAPTCHAs in a .NET Worker Service That Stays Up

Bir .NET Worker Service içinde CAPTCHA çözmek için tek bir CapSkipClient’ı singleton olarak kaydedin, RecaptchaAsync’i ExecuteAsync içinden durdurma token’ıyla (stoppingToken) çağırın ve CapSkip’in istisnalarını döngünün içinde yakalayın. Worker şablonunun atladığı kısım bu sonuncusudur ve göründüğünden daha önemlidir. .NET 6’dan beri bir BackgroundService içindeki işlenmemiş bir istisna, host’un tamamını durdurur. Ardından süreç 0 koduyla çıkar; bu yüzden bir Windows Service’in kurtarma seçenekleri onu asla yeniden başlatmaz. Açılışta, CapSkip henüz açık olmadığı için fırlatılan tek bir NetworkException buna yeter. Bu worker service CAPTCHA kurulumu reCAPTCHA arkasındaki bir siteye 15 dakikada bir giriş yapar, henüz ayağa kalkmamış bir çözücüyü bekler, saniyenin küçük bir kesrinde durur ve CapSkip’in yanında Windows Service olarak çalışır.

Neye ihtiyacınız var

  • reCAPTCHA çözücüsü açık hâlde bir Windows makinesinde çalışan CapSkip.
  • SDK olarak .NET 8 veya üzeri. Aşağıdaki kod .NET 8 ve .NET 10 üzerinde çalıştırıldı.
  • NuGet’ten CapSkip paketi ve aşağıdaki Program.cs dosyasının Windows Service olarak çalışmak için kullandığı Microsoft.Extensions.Hosting.WindowsServices.
  • Servisi kurduğunuz makinede yönetici hakları; yalnızca o adım için.
  • Çözücü için bir adres. Local modunda CapSkip 127.0.0.1 üzerinden yalnızca o cihaza yanıt verir. Server modunda ise ağ adresinizi veya genel IP’nizi dinler, böylece başka bir makinedeki bir worker onu API üzerinden çağırabilir. İkisi de şurada ayarlanır: bağlantı ayarları.
dotnet new worker -n PortalWorker
cd PortalWorker
dotnet add package CapSkip
# Update Hosting before the next line: the template pins an older version,
# and the Windows Services package fails to restore against it (NU1605).
dotnet add package Microsoft.Extensions.Hosting
dotnet add package Microsoft.Extensions.Hosting.WindowsServices

Son iki satırın sırası önemlidir. Worker şablonu, SDK’nızla birlikte gelen Microsoft.Extensions.Hosting sürümünü sabitler; güncel Windows Services paketi ise daha yeni bir sürüm ister. Önce Windows Services paketini eklerseniz restore işlemi, bir paket sürüm düşürme (package downgrade) hatası olan NU1605 ile başarısız olur. Önce Hosting paketini güncellemek bunu önler ve şablonda başka hiçbir şey değişmez.

1. Adım: Program.cs dosyasında tek bir istemci kaydedin

CapSkipClient istek başına hiçbir durum tutmaz ve her şeyi paketin içindeki tek bir statik HttpClient üzerinden gönderir; bu yüzden tek bir örnek bütün sürece hizmet edebilir. Onu yapılandırmadan bir kez oluşturun ve singleton olarak kaydedin.

// Program.cs
using CapSkip;
using PortalWorker;

var builder = Host.CreateApplicationBuilder(args);

// A Windows Service when the service manager starts it, an ordinary
// console app when you run it yourself.
builder.Services.AddWindowsService(options => options.ServiceName = "PortalWorker");

// One client for the whole process. The SDK reads no environment
// variables, but the host's configuration does, so CAPSKIP_HOST can come
// from the environment or from appsettings.json.
var config = builder.Configuration;
builder.Services.AddSingleton(new CapSkipClient(
    apiKey: config["CAPSKIP_API_KEY"] ?? "capskip",
    host: config["CAPSKIP_HOST"] ?? "127.0.0.1",
    port: int.Parse(config["CAPSKIP_PORT"] ?? "8080")));
builder.Services.AddSingleton<PortalLogin>();
builder.Services.AddHostedService<Worker>();

builder.Build().Run();

Programı kendiniz başlattığınızda AddWindowsService hiçbir şey yapmaz; böylece aynı derleme, test ederken bir konsolda, daha sonra da servis olarak çalışır. CapSkip SDK kendi başına hiçbir ortam değişkeni okumaz, ancak Host.CreateApplicationBuilder’ın arkasındaki yapılandırma nesnesi tüm ortam değişkenlerini okur; bu yüzden CAPSKIP_HOST ortamdan verildiğinde çalışır. appsettings.json içinde bir anahtar olarak da çalışır ve bir servis için daha kolay olan yer burasıdır, çünkü bir servisin ortamını düzenlemek zahmetlidir. Windows bir servisi başlattığında servisin çalışma klasörü System32 olur. Host bu durumda programın kendi klasörünü içerik kökü (content root) olarak kullanır; böylece exe’nin yanına yayımlanan appsettings.json dosyasını okur.

2. Adım: CAPTCHA’yı çözen iş

İşin kendisi sıradan bir koddur. Giriş sayfasını çeker, sitekey’i ve gizli alanları okur, CapSkip’ten bir token ister ve formu gönderir. Onu kendi sınıfında tutun; böylece worker yalnızca işin ne zaman çalışacağına ve bir hatanın ne anlama geldiğine karar verir.

// PortalLogin.cs
using System.Net;
using System.Text.RegularExpressions;
using CapSkip;

namespace PortalWorker;

public sealed class PortalLogin(CapSkipClient solver, ILogger<PortalLogin> logger)
{
    const string PageUrl = "https://example.com/login";

    public async Task RunAsync(CancellationToken ct)
    {
        // A new HttpClient per run keeps its own cookies, so one run's
        // session never leaks into the next.
        using var http = new HttpClient();
        var html = await http.GetStringAsync(PageUrl, ct);
        var sitekey = Regex.Match(html, "data-sitekey=\"([^\"]+)\"").Groups[1].Value;

        // The form's hidden fields, such as a CSRF token.
        var fields = new Dictionary<string, string>();
        foreach (Match input in Regex.Matches(html, "<input[^>]*type=\"hidden\"[^>]*>"))
        {
            var name = Regex.Match(input.Value, "name=\"([^\"]+)\"").Groups[1].Value;
            var value = Regex.Match(input.Value, "value=\"([^\"]*)\"").Groups[1].Value;
            fields[name] = WebUtility.HtmlDecode(value);
        }

        // Pass the token on, so stopping the service cancels the solve.
        var result = await solver.RecaptchaAsync(sitekey, PageUrl, cancellationToken: ct);

        fields["username"] = "YOUR_USERNAME";
        fields["password"] = "YOUR_PASSWORD";
        fields["g-recaptcha-response"] = result.Code;
        using var post = new HttpRequestMessage(HttpMethod.Post, PageUrl)
        {
            Content = new FormUrlEncodedContent(fields),
        };
        // HttpClient sends no Referer; strict servers refuse a post without one.
        post.Headers.Referrer = new Uri(PageUrl);
        using var response = await http.SendAsync(post, ct);
        if (response.IsSuccessStatusCode)
            logger.LogInformation("Login posted: {Status}", (int)response.StatusCode);
        else
            logger.LogWarning("Login rejected: {Status}", (int)response.StatusCode);
    }
}

Gözden kaçması kolay üç ayrıntı var. Her çalıştırmada oluşturulan yeni bir HttpClient, çerezleri yalnızca o çalıştırma için saklar; böylece form geri gönderildiğinde oturum çerezi ile gizli alanlardaki CSRF token’ı hâlâ eşleşir. HttpClient Referer başlığı göndermez ve Django gibi framework’ler bu başlık olmayan bir HTTPS form gönderimini reddeder; bu yüzden iş onu kendisi ayarlar. Son olarak durdurma token’ı RecaptchaAsync’e gider; servisin çözümü beklemek yerine çözümün ortasında durabilmesini sağlayan da budur.

Göndermeden hemen önce çözün. Bir reCAPTCHA token’ının süresi, verildikten iki dakika sonra dolar; dolayısıyla erken çözüp sonra gönderen bir worker geçersiz bir token gönderir. Buradaki sayfa ayrıştırma bilerek küçük tutulmuştur. Görünmez widget’lar, Enterprise anahtarları ve diğer varyantlar şurada ele alınıyor: C# ile reCAPTCHA v2 çözme rehberi.

3. Adım: başarısız bir çözümden sağ çıkan bir döngü

Worker service CAPTCHA hatalarının çoğu bu dosyada yaşar. Şablonun döngüsünde hiç try bloğu yoktur; bu, saati loglamak için sorun değildir ama başka bir programla konuşan her şey için ölümcüldür.

// Worker.cs
using CapSkip;

namespace PortalWorker;

public sealed class Worker(PortalLogin job, ILogger<Worker> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        try
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                var wait = TimeSpan.FromMinutes(15);
                try
                {
                    await job.RunAsync(stoppingToken);
                }
                catch (NetworkException ex) when (!stoppingToken.IsCancellationRequested)
                {
                    // CapSkip is not running yet, as after a reboot. Try again soon.
                    logger.LogWarning("CapSkip unreachable: {Message}", ex.Message);
                    wait = TimeSpan.FromSeconds(30);
                }
                catch (CapSkipError ex) when (!stoppingToken.IsCancellationRequested)
                {
                    logger.LogWarning("Solve failed: {Type}: {Message}", ex.GetType().Name, ex.Message);
                }
                catch (Exception ex) when ((ex is HttpRequestException or TaskCanceledException)
                                           && !stoppingToken.IsCancellationRequested)
                {
                    // The site failed, or HttpClient gave up on it after 100 s.
                    logger.LogWarning("The site did not answer: {Message}", ex.Message);
                }

                await Task.Delay(wait, stoppingToken);
            }
        }
        catch (Exception) when (stoppingToken.IsCancellationRequested)
        {
            // Stopping. A cancelled poll ends in TaskCanceledException and a
            // cancelled submit in NetworkException; neither is a failure.
        }
        catch (Exception ex)
        {
            logger.LogCritical(ex, "Worker failed");
            // Exit non-zero, so the service's recovery options restart it.
            // A host that stops on its own exits with code 0.
            Environment.Exit(1);
        }
    }
}

Her catch bloğu tek bir soruyu yanıtlar. NetworkException, CapSkip’in yanıt vermediği anlamına gelir, genellikle henüz çalışmadığı için; bu yüzden worker 15 dakika yerine 30 saniye sonra yeniden dener. Diğer her CapSkipError, CapSkip’ten gelen bir hata kodunu içeren bir ApiException gibi başarısız bir çözümdür; bu yüzden worker onu loglar ve bir sonraki çalıştırmayı bekler. Ortada bir durdurma yokken gelen bir HttpRequestException ya da TaskCanceledException siteden gelir. Buradaki bir TaskCanceledException, HttpClient’ın varsayılan 100 saniyesinin ardından vazgeçtiği anlamına gelir. Reddedilen bir giriş dahil her hata Warning seviyesinde loglanır; bunun nedenini servis bölümü açıklıyor.

Durdurma token’ına göre filtrelemek, bir durdurmanın hata gibi görünmesini önler. Ardından dıştaki catch bloğu durdurmanın kendisini, son catch bloğu da ayrıştırmadaki bir kod hatası gibi kimsenin beklemediği her şeyi ele alır. Son blok istisnayı loglar ve süreci 1 çıkış koduyla sonlandırır; Windows’un servisi yeniden başlatmasını sağlayan tek ayrıntı da budur.

Tek bir istisna neden host’un tamamını durdurur

Host, ExecuteAsync’i başlatır ve döndürdüğü Task’ı izler. .NET 6’dan beri bu Task başarısız olursa host, varsayılanı StopHost olan HostOptions.BackgroundServiceExceptionBehavior ayarını uygular. Şablonun döngüsünü alın ve CapSkip’i kapatın. .NET 10 üzerindeki bir çalıştırma bu durumda altında NetworkException bulunan bir BackgroundService failed satırı loglar, ardından bir BackgroundService istisna fırlattığı için host’un durduğunu söyleyen kritik bir satır gelir. Sonra host kapanır. Reddedilen tek bir bağlantı yeter ve worker ortadan kalkar.

Bunu bir servis için çirkin hâle getiren şey çıkış kodudur. Host temiz bir şekilde durur, dolayısıyla süreç 0 koduyla çıkar; bunu hem .NET 8 hem de .NET 10 üzerinde ölçtük. Windows bir servisin kurtarma seçeneklerini yalnızca servis başarısız olduğunda uygular; temiz bir çıkış ise başarısızlık değildir, bu yüzden servis öylece durmuş hâlde kalır. Microsoft’un kendi BackgroundService’i Windows Service olarak çalıştırma rehberi de tam bu nedenle Environment.Exit’i sıfırdan farklı bir kodla çağırmayı önerir; yukarıdaki worker da bunu son catch bloğunda yapar. Diğer ayar olan Ignore ise daha kötüdür: host, içinde ölü bir worker ile çalışmaya devam eder; sağlıklı görünen ama hiçbir şey yapmayan bir servis.

Bir çözümün ortasında temiz bir şekilde durmak

Windows servisi durdurduğunda ya da konsolda Ctrl+C tuşlarına bastığınızda host, durdurma token’ını iptal eder ve ExecuteAsync’in bitmesini bekler. Varsayılanı 30 saniye olan HostOptions.ShutdownTimeout süresi kadar bekler, sonra her hâlükârda durur. Bir reCAPTCHA çözümü bundan çok daha uzun sürebilir, çünkü SDK 300 saniyeye kadar yoklama yapar. Testimizde token verilmediğinde, 60 saniyelik bir çözümün 5. saniyesinde istenen bir durdurma 30 saniye sürdü: host ShutdownTimeout süresinin tamamını bekledi ve ardından çözümü yarıda bıraktı. Token iletildiğinde aynı durdurma yaklaşık 20 milisaniye sürdü.

İptal, nereye denk geldiğine bağlı olarak size iki biçimde ulaşır. SDK’nın yoklaması sırasında, yoklamalar arasındaki beklemeden gelen bir TaskCanceledException ile sona erer. SDK görevi gönderirken, yani CapSkip’e ilk isteğini yaparken denk gelirse SDK onu bir NetworkException içine sarar; bu da bir OperationCanceledException değil, bir CapSkipError’dır. Filtre olmadan bu ikinci durum bir çökme gibi görünür. Gönderimin ortasında bir worker’ı durdurduk ve host, sıradan bir kapanışta, altında The operation was canceled bulunan bir BackgroundService failed satırı logladı. Yukarıdaki her catch bloğunun önce durdurma token’ını kontrol etmesinin nedeni budur.

4. Adım: Windows Service olarak çalıştırın

Worker’ı bir klasöre yayımlayın ve exe dosyasını servis yöneticisine kaydedin. Aşağıdaki komutların dotnet publish dışındaki hepsi, yönetici olarak açılmış (elevated) bir PowerShell gerektirir.

# In an elevated PowerShell. Paths are examples.
dotnet publish -c Release -r win-x64 -o C:\Services\PortalWorker
sc.exe create PortalWorker binpath= "C:\Services\PortalWorker\PortalWorker.exe" start= delayed-auto
sc.exe failure PortalWorker reset= 86400 actions= restart/60000/restart/60000/restart/60000
sc.exe start PortalWorker

Environment.Exit(1) ile eşleşen satır failure satırıdır. Windows’a, bir hatadan bir dakika sonra servisi yeniden başlatmasını söyler. Windows sonraki her hatada son eylemi tekrarlar, böylece servis geri gelmeye devam eder; hata sayacı da hatasız geçen bir günün ardından sıfırlanır. Gecikmeli otomatik başlatma, servisi açılışın en başında değil, diğer otomatik servislerden kısa bir süre sonra başlatır. CapSkip’in kendisi bir masaüstü uygulamasıdır; bu yüzden yeniden başlatmadan sonra ancak biri Windows’ta oturum açtığında çalışır ve servis bundan çok önce başlayabilir. Worker bu durumu kendi başına halleder: çözücü yanıt verene kadar her 30 saniyede bir CapSkip unreachable loglar. Her yeniden deneme giriş sayfasını da yeniden yükler; Windows oturum açma ekranında beklerken bu, saatte yaklaşık 120 kez demektir, bu yüzden site bunu fark edecekse 30 saniyeyi uzatın. Bir servis ile bir masaüstü uygulaması farklı Windows oturumlarında çalışır, ancak 127.0.0.1 oturumlar arasında bağlanır; bu yüzden Local modu ikisi arasında çalışır.

Servis olarak çalışırken host loglarını Windows Event Log’a, Application log içine gönderir ve varsayılan olarak oraya yalnızca Warning seviyesindeki ve üzerindeki kayıtlar ulaşır. Worker’ın hatalarını uyarı olarak loglamasının nedeni budur: Information seviyesindeki bir satır Event Viewer’da asla görünmezdi.

Tek çalıştırmada birden fazla hesap

Her turda birden fazla hesapla giriş yapmak için daha fazla worker başlatmak yerine iş içinde Parallel.ForEachAsync ile döngü kurun. MaxDegreeOfParallelism değerini 10 yapın, çünkü CapSkip varsayılan olarak aynı anda 10 reCAPTCHA çözer (reCAPTCHA ayarlarındaki Max. Threads) ve geri kalanını kuyruğa alır. Durdurma token’ını ParallelOptions nesnesinin CancellationToken özelliğinde verin ve her yinelemenin aldığı token’ı RecaptchaAsync’e iletin; böylece bir durdurma tüm çözümleri aynı anda iptal eder. Her hesabın hatalarını, worker ile aynı filtrelerle, döngü gövdesinin içinde yakalayın. Bir hesaptan kaçan bir istisna, o çalıştırmadaki diğer tüm hesapların çözümlerini iptal eder. CapSkipClient singleton’ını bu çağrılar arasında paylaşmak güvenlidir. Yukarıdaki işin yaptığı gibi her hesaba kendi HttpClient’ını verin; böylece hiçbir iki oturum aynı çerezi paylaşmaz.

Çözücüyü başka bir makinede çalıştırmak

Worker ve CapSkip aynı Windows bilgisayarı paylaştığı sürece 127.0.0.1 doğru adrestir. Worker başka bir yerde, örneğin başka bir Windows sunucusunda, bir sanal makinede (VM) ya da bir konteyner sunucusunda çalışabilir. O zaman CapSkip’i Server moduna alın; böylece ağ adresinizi veya genel IP’nizi dinler. Ardından appsettings.json dosyasında ya da ortamda CAPSKIP_HOST değerini o adrese ayarlayın. Rota internetten geçiyorsa statik bir genel IP kullanın ve API anahtarı doğrulamasını açın. CapSkip, Server modu başladığında portunu her adrese açar; portu yalnızca beklediğiniz makinelere açık tutmak için diğerlerine yönelik bir Windows Firewall engelleme kuralı ekleyin. Çözücü hâlâ sizin kendi Windows makinenizdir ve çözüm başına ücretlendirme hâlâ yoktur.

Sık görülen worker service CAPTCHA hataları

GördüğünüzNedenDüzeltme
Windows Services paketi eklenirken NU1605, yani algılanan bir paket sürüm düşürmeŞablon, Microsoft.Extensions.Hosting paketini Windows Services paketinin ihtiyaç duyduğundan daha eski bir sürüme sabitliyorÖnce Microsoft.Extensions.Hosting paketini, ardından Windows Services paketini ekleyin
BackgroundService failed, ardından StopHost ile ilgili kritik bir satır ve worker ortadan kalkıyorBir istisna ExecuteAsync dışına kaçtı; varsayılan StopHost davranışı host’u durdurduCapSkipError’ı ve sitenin istisnalarını döngünün içinde yakalayın
Servis bir hatadan sonra durdu ve Windows onu hiç yeniden başlatmadıStopHost ile durdurulan bir host 0 koduyla çıkar ve bu, Windows açısından bir başarısızlık değildirBeklenmeyen hataları Environment.Exit(1) ile sonlandırın ve kurtarmayı sc.exe failure ile ayarlayın
Normal bir durdurmada, altında The operation was canceled bulunan BackgroundService failedDurdurma, görev CapSkip’e gönderilirken geldi ve SDK bunu NetworkException içine sararHer catch bloğunu durdurma token’ına göre filtreleyin
Servisi durdurmak 30 saniye sürüyorÇözüm iptali yok sayıyor, bu yüzden host ShutdownTimeout süresinin dolmasını bekliyorDurdurma token’ını RecaptchaAsync’e ve her HTTP çağrısına verin
Her yeniden başlatmadan sonra logda CapSkip unreachableServis, kimse oturum açıp CapSkip’i çalıştırmadan önce başladıDüzeltme gerekmez: worker, CapSkip yanıt verene kadar her 30 saniyede bir yeniden dener
Hedef makinenin bağlantıyı etkin olarak reddettiğini söyleyen bir NetworkExceptionCapSkip çalışmıyor ya da host veya port yanlışCapSkip’i başlatın ve CAPSKIP_HOST ile CAPSKIP_PORT değerlerini bağlantı ayarlarıyla karşılaştırın
Site token’ı reddediyor ya da yeniden giriş sayfasıyla yanıt veriyorToken’ın süresi gönderimden önce doldu ya da gönderimde çerez, gizli bir alan veya Referer eksiktiGöndermeden hemen önce çözün ve sayfayı çeken HttpClient ile gönderin
Event Viewer’da worker’dan hiçbir kayıt yokEvent Log varsayılan olarak yalnızca Warning ve üzerini alırHataları Warning seviyesinde loglayın ya da appsettings.json içinde EventLog için Information LogLevel değeri ayarlayın
ERROR_KEY_DOES_NOT_EXIST veya ERROR_WRONG_USER_KEY içeren bir ApiExceptionCapSkip’te API anahtarı doğrulaması açık ve worker varsayılan anahtarı gönderdi ya da hiç anahtar göndermediCAPSKIP_API_KEY değerini CapSkip’te yapılandırılmış bir anahtara ayarlayın

FAQ

Bunun yerine BackgroundServiceExceptionBehavior değerini Ignore yapmalı mıyım?

Hayır. Ignore ile host istisnayı bir kez hata olarak loglar ve devam eder, ancak ExecuteAsync bir daha asla çağrılmaz; bu yüzden süreç içinde hiçbir şey olmadan çalışmaya devam eder. Windows servisi çalışıyor olarak gösterir ve biri fark edene kadar hiçbir giriş yapılmaz. Beklenen hataları döngüde yakalayın, geri kalanların da süreci sıfırdan farklı bir çıkış koduyla sonlandırmasına izin verin.

Aynısı ASP.NET Core’daki bir hosted service için de geçerli mi?

Evet. Bir ASP.NET Core uygulaması aynı Generic Host üzerinde çalışır. Dolayısıyla bir web uygulamasındaki BackgroundService de aynı StopHost varsayılanına ve aynı 30 saniyelik kapanış zaman aşımına sahiptir; işlenmemiş bir CapSkip istisnası da tüm web sitesini beraberinde çökertir. Yukarıdaki döngü orada değişmeden çalışır, ancak içindeki Environment.Exit(1) çağrısının tüm web uygulamasını da sonlandırdığını unutmayın.

Hangfire ne zaman bir worker’dan daha uygun bir seçimdir?

Çözümler, bir zamanlayıcıya bağlı tek bir görev değil de başka kodların kuyruğa eklediği işler olduğunda. Hangfire her işi saklar, kendi zamanlamasıyla yeniden dener ve geçmişi bir dashboard’da gösterir; düz bir worker ise bunları size bırakır. Hangfire’ın yeniden deneme filtresi, CapSkip’in istisnalarıyla ilgili ayrıca özen ister; bu konu şurada açıklanıyor: Hangfire işlerinde CAPTCHA çözme rehberi.

Worker, CapSkip’ten farklı bir makinede çalışabilir mi?

Evet. Bağlantı ayarlarından CapSkip’i Server moduna alın; böylece loopback yerine bir ağ adresini dinler. Ardından CAPSKIP_HOST değerini o adrese ayarlayın. Worker bundan sonra porta ulaşabilen her makineden onu aynı HTTP API üzerinden çağırır; porta ulaşmaması gereken adresler için de bir Windows Firewall engelleme kuralı ekleyin. Çözücü sizin sahip olduğunuz donanımda kalır, dolayısıyla çözümlerin nasıl sayıldığı konusunda hiçbir şey değişmez.

Kısa özet

Bir worker service CAPTCHA işi, şablonun size vermediği üç şeye ihtiyaç duyar. CapSkipError’ı ve sitenin istisnalarını döngünün içinde yakalayın, çünkü dışarı kaçan tek bir istisna host’u durdurur ve süreç 0 koduyla çıkar. Durdurma token’ını RecaptchaAsync’e verin ve her catch bloğunu ona göre filtreleyin; böylece bir durdurma 30 saniye değil bir an sürer ve asla çökme olarak loglanmaz. Son olarak gerçekten beklenmeyen hataları Environment.Exit(1) ile sonlandırın; böylece servisin kurtarma seçenekleri onu yeniden başlatabilir. CapSkip ayakta değilken her 30 saniyede bir yeniden deneyin ve worker çözücünün makinesinden ayrıldığında Server moduna geçin.

Aylarca her 15 dakikada bir çalışan bir worker binlerce çözüm yapar. Kendi Windows makinenizdeki bir captcha atlatma aracı tam da burada kendini amorti eder: çözüm sayısı hiçbir zaman bir faturada görünmez.