在后台线程中处理时,C#UI变得无响应

在后台线程中处理时,C#UI变得无响应,c#,C#,我正在同时测试大量代理服务器的有效性。在测试过程中,会引发并捕获许多异常。尽管我在后台线程中进行测试,但除非我使用SemaphoreSlim对象来控制并发性,否则我的UI将变得无响应 我知道这是一个自我强加的瓶颈,当使用更大的代理列表进行测试时,我希望有更好的方法来解决这个问题 private void ValidateProxiesButton_Click(object sender, EventArgs e) { new Thread(async () => {

我正在同时测试大量代理服务器的有效性。在测试过程中,会引发并捕获许多异常。尽管我在后台线程中进行测试,但除非我使用
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);
}
private void ValidateProxiesButton\u单击(对象发送方,事件参数e)
{
新线程(异步()=>
{
Thread.CurrentThread.IsBackground=true;
等待ValidateProxiesAsync(代理人、法官、测试,10);
}).Start();
}
公共异步任务ValidateProxiesAsync(IEnumerable代理、IEnumerable判断、IEnumerable测试=null、int-maxConcurrency=20)
{
if(proxies.Count()==0)
{
抛出新ArgumentException(“代理列表为空”);
}
foreach(代理中的var代理)
{
proxy.Status=ProxyStatus.Queued;
}
//获取外部IP以检查代理是否匿名。
var publicIp=wait WebUtility.GetPublicIP();
foreach(法官中的var法官)
{
法官:无效=公共知识产权;
}
等待ValidateTestsAsync(judges.ToList());
var validJudges=judges.ToList().GetValidTests();
if(validJudges.Count==0)
{
抛出新的ArgumentException(“未找到有效的法官”);
}
如果(测试!=null)
{
等待ValidateTestsAsync(tests.ToList());
}
var信号量=新信号量lim(maxConcurrency);
var tasks=新列表();
foreach(代理中的var代理)
{
tasks.Add(Task.Run)(异步()=>
{
wait semaphore.WaitAsync();
proxy.Status=ProxyStatus.Testing;
var isValid=wait proxy.testvaliditysync((IProxyTest)validJudges.GetRandomItem());
proxy.Status=isValid?ProxyStatus.Valid:ProxyStatus.Invalid;
semaphore.Release();
}));
}
等待任务。何时(任务);
}
内部proxy.testValiditySync方法

public异步任务TestValidityAsync(IProxyTest测试,int-timeoutSeconds=30)
{
尝试
{
var req=WebRequest.Create(test.URL);
req.Proxy=newWebProxy(this.ToString());
var respBody=wait WebUtility.GetResponseStringAsync(req).TimeoutAfter(新的时间跨度(0,0,timeoutSeconds));
if(响应体包含(测试验证))
{
返回true;
}
其他的
{
返回false;
}
}
捕获(例外)
{
返回false;
}
}

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

资料来源:

private async void按钮1\u单击(对象发送方,事件参数e)
{
var downloader=新转换块(
url=>下载(url),
新的ExecutionDataflowBlockOptions{MaxDegreeOfParallelism=200}
);
var buffer=new BufferBlock();
downloader.LinkTo(缓冲区);
var url=新列表();
对于(int i=0;i<100000;i++)
{
URL.Add($)http://example{i} (www.com);;
}
foreach(url中的变量url)
downloader.Post(url);
//或者等待downloader.SendAsync(url);
downloader.Complete();
等待下载。完成;
IList反应;
if(buffer.TryReceiveAll(输出响应))
{
//过程响应
}
}
私有WebResponse下载(字符串url)
{
webresp=null;
尝试
{
var req=WebRequest.Create(url);
resp=req.GetResponse();
}
捕获(例外)
{
}
返回响应;
}
}

MickyD我也在没有新线程的情况下尝试过,结果相同。网络请求也算是CPU限制吗?“CPU绑定意味着程序受到CPU或中央处理器的限制,而I/O绑定意味着程序受到I/O或输入/输出的限制,如读写磁盘、网络等。”@MickyD我不确定网络请求处理是否算作CPU绑定(我认为不是),因此,我不确定它在这里是如何应用的??我唯一能想到的另一件事是,您的代码的其他部分可能正在踢出许多短UI捕获
wait
s。当它们完成时,它们会在等待之后中断UI以执行该行。太多可能是个问题。”“好的。让我们知道你进展如何。请记住,如果在此期间你自己发现了错误,你可以发布自己的答案,甚至接受:)我看到你的另一个问题(现在已删除),你从那里开始10万个任务,每个任务都会引发异常。在这个问题的情况下,您的CPU正忙于处理这个问题,这降低了UI的响应能力(因为UI也需要CPU)。您可以通过查看windows process explorer并查看进程的CPU消耗有多高来验证这一点
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;
    }
}
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;
}
}