首页 > 解决方案 > C# UI 在后台线程处理期间变得无响应

问题描述

我正在同时测试大量代理服务器的有效性。在此测试期间,会引发和捕获许多异常。尽管我在后台线程中进行测试,但除非我使用SemaphoreSlim对象来控制并发性,否则我的 UI 会变得无响应。

我知道这是一个自我强加的瓶颈,当使用更大的代理列表进行测试时,我希望可能有更好的方法来解决这个问题。

private void ValidateProxiesButton_Click(object sender, EventArgs e)
{
    new Thread(async () =>
    {
        Thread.CurrentThread.IsBackground = true;
        await ValidateProxiesAsync(proxies, judges, tests, 10);
    }).Start();
}


public async Task ValidateProxiesAsync(IEnumerable<Proxy> proxies, IEnumerable<ProxyJudge> judges, IEnumerable<ProxyTest> tests = null, int maxConcurrency = 20)
{
    if (proxies.Count() == 0)
    {
        throw new ArgumentException("Proxy list empty.");
    }

    foreach (var proxy in proxies)
    {
        proxy.Status = ProxyStatus.Queued;
    }

    //Get external IP to check if proxy is anonymous.
    var publicIp = await WebUtility.GetPublicIP();
    foreach (var judge in judges)
    {
        judge.Invalidation = publicIp;
    }

    await ValidateTestsAsync(judges.ToList<IProxyTest>());
    var validJudges = judges.ToList<IProxyTest>().GetValidTests();
    if (validJudges.Count == 0)
    {
        throw new ArgumentException("No valid judges found.");
    }

    if (tests != null)
    {
        await ValidateTestsAsync(tests.ToList<IProxyTest>());
    }

    var semaphore = new SemaphoreSlim(maxConcurrency);
    var tasks = new List<Task>();
    foreach (var proxy in proxies)
    {
        tasks.Add(Task.Run(async () =>
        {
            await semaphore.WaitAsync();
            proxy.Status = ProxyStatus.Testing;
            var isValid = await proxy.TestValidityAsync((IProxyTest)validJudges.GetRandomItem());
            proxy.Status = isValid ? ProxyStatus.Valid : ProxyStatus.Invalid;
            semaphore.Release();
        }));
    }

    await Task.WhenAll(tasks);
}

内部 proxy.TestValidityAsync 方法

public async Task<bool> TestValidityAsync(IProxyTest test, int timeoutSeconds = 30)
{
    try
    {
        var req = WebRequest.Create(test.URL);
        req.Proxy = new WebProxy(this.ToString());
        var respBody = await WebUtility.GetResponseStringAsync(req).TimeoutAfter(new TimeSpan(0, 0, timeoutSeconds));
        if (respBody.Contains(test.Validation))
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    catch (Exception)
    {
        return false;
    }
}

标签: c#

解决方案


所以我找到了一个可行的解决方案,就是将 TPL Dataflow NuGet 包添加到我的项目中,然后使用 TransformBlock 类。当我这样做时,即使我正在处理大量经常引发异常的并发请求,我的 UI 也会保持非常灵敏。下面的代码是概念证明,我会在翻译它以用于我的项目时对其进行更新。

来源:限制异步任务

private async void button1_Click(object sender, EventArgs e)
{

    var downloader = new TransformBlock<string, WebResponse>(
            url => Download(url),
            new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 200 }
        );

    var buffer = new BufferBlock<WebResponse>();
    downloader.LinkTo(buffer);

    var urls = new List<string>();
    for (int i = 0; i < 100000; i++)
    {
        urls.Add($"http://example{i}.com");
    }

    foreach (var url in urls)
        downloader.Post(url);
    //or await downloader.SendAsync(url);

    downloader.Complete();
    await downloader.Completion;

    IList<WebResponse> responses;
    if (buffer.TryReceiveAll(out responses))
    {
        //process responses        
    }
}

private WebResponse Download(string url)
{
    WebResponse resp = null;
    try
    {
        var req = WebRequest.Create(url);
        resp = req.GetResponse();
    }
    catch (Exception)
    {

    }
    return resp;
}
}

推荐阅读