Diskuze: Ověření dostupnosti stovek webů
V předchozím kvízu, Test znalostí C# .NET online, jsme si ověřili nabyté zkušenosti z kurzu.


Jak spouštět kód každou minutu vím, jde mi jen o vykonání v co nejrychlejším termínu. Díky
No můžu ti zkusit ukázat rozdíl. Kód je pouze ukázkový, ale na svůj projekt si ho převedeš jednoduše.
Takhle by teoreticky vypadalo stahování nějaké stránky async. Celkový čas by byl cca. stejný. U tohoto čekáme, než se stáhne jedna stránka, pak stahujeme druhou, atd.
private async Task DownloadAsync()
{
List<string> websites PrepData();
foreach (string web in websites)
{
WebsiteDataModel results = await Task.Run(() => DownloadWebsite(web));
ReportWeb(results)
}
}
Zrychlit bychom to mohli tak, že budeme stahovat všechny současně a čekat budeme jenom na tu nejdelší.
private async Task DownloadAsyncParallel()
{
List<string> websites PrepData();
List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();
foreach (string web in websites)
{
tasks.Add(Task.Run(() => DownloadWebsite(web)));
}
var results = await Task.WhenAll(tasks);
foreach (var item in results)
{
ReportWeb(results);
}
}
Nebo tohle všechno vysvětluje ještě tohle video: https://www.youtube.com/watch?… . Rozhodně stojí za to se na něho podívat, řeší tam skoro stejný problém.
don.jarducius:30.10.2018 12:06
Ahoj,
nad Listem, potažmo nad IEnumerable je možnost volat AsParallel(), potom lze
vykonávat parallelně operace s Listem, např. .ForAll(x=> { /* obsluha
práce s položkou */ }); Automaticky by se měl spustit efektivní počet
vláken vzhledem k dostupným jádrům.
Problém ale může být při pokusu o zápis do databáze pomocí entit -
paralelní přístup ke kolekcím což je problém. Tomu se dá vyhnout buď
pomocí zámků, nebo pomocí DbContext.Database.ExecuteSqlCommand … a
vkládat data přímo pomocí SQL.
díky za rady. Během víkendu to budu zkoumat
Adam Gajdečka:2.11.2018 12:34
Povedlo se mi to upravit podle tvého příkladu a je to rychlé jako blesk. Jen nevím, jak to každou minutu spustit, používám HangFire.
public async Task CheckWebsitesAsync()
{
List<Task<WebsiteStateRecord>> tasks = new List<Task<WebsiteStateRecord>>();
foreach (var web in _dbContext.Websites.ToList())
{
tasks.Add(Task.Run(() => CheckWebsiteAsync(web)));
}
var results = await Task.WhenAll(tasks);
_dbContext.WebsiteStateRecords.AddRange(results);
_dbContext.SaveChanges();
}
Toto mi shodí web, začne neodpovídat a potom hodí Bad Request
RecurringJob.AddOrUpdate(() => _websiteBLL.CheckWebsitesAsync(), Cron.MinuteInterval(1));
Díky
Co jsem načetl, tak Hangfire nepodporuje async joby.(ale podporuje to ta Pro verze, která ovšem stojí 500 USD ročně) Jak to tedy nejlépe spouštět? Nejraději nějakou alternativu k Hangfire, ale co jsem našel, tak nebylo moc friendly.
don.jarducius:2.11.2018 20:23
Jsem trochu zmatený, kde a jak hostuješ tu svoji aplikaci? Hosting/VPS/Housing ?
Adam Gajdečka:2.11.2018 21:04
https://www.zonercloud.cz/…loud-server/
Mám tam Windows Server 2016 - VPS
Promiň, ale v tom případě nechápu, proč si to komplikuješ s
Hangfire.
Vytvoř si konzolovou aplikaci (resp. obal si "očuchávací" funkci
konzolovkou), která při spuštění "očuchá" potřebné weby a nastav si
její spouštění do standardního plánovače úloh.
Nebo si vytvoř windows service, který se o to bude starat - což je jen pár
řádků navíc oproti konzolovce...
Adam Gajdečka:3.11.2018 19:22
Hangfire bylo nejsnadnější použít v mých projektech. Rád bych tedy to udělal, jak popisuješ. Vůbec netuším jak začít. Jak spustit z consolové aplikace kód v mé webové aplikaci? Nemáš nějaké odkazy na video či nějaké tutorialy? Děkuji
Prostě ten kód zkopíruješ do té konzolovky. Klasický net framework ti
buidle exe, pokud chceš použít core, ten vy vybuilduje dll, které spustíš
pomocí příkazu dotnet [název].dll.
Trochu jsem to zjednodušil, teď by to defakto mělo jít i v HangFire.
Kód konzolovky v net core
using System;
using System.Diagnostics;
using System.Linq;
using System.Net;
namespace ConsoleApp4
{
class Program
{
static void Main(string[] args)
{
using (var c = new Checker())
{
c.CheckWebsites();
}
}
}
public class Checker : IDisposable
{
private WebSiteDbContext _dbContext;
public Checker()
{
_dbContext = new WebSiteDbContext();
}
public void Dispose() {
_dbContext.Dispose();
}
public void CheckWebsites()
{
var weby = _dbContext.Websites.ToList();
var results = weby.AsParallel().Select(x => CheckWebsiteAsync(x)).ToList();
_dbContext.WebsiteStateRecords.AddRange(results);
_dbContext.SaveChanges();
}
public WebsiteStateRecord CheckWebsiteAsync(Website web)
{
WebsiteStateRecord record = new WebsiteStateRecord
{
WebsiteId = web.Id,
Date = DateTime.Now
};
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(web.Url);
request.AllowAutoRedirect = true;
request.Timeout = 30000;
request.Method = "HEAD";
try
{
HttpWebResponse httpRes = (HttpWebResponse)request.GetResponse();
record.HttpStatusCode = (int)httpRes.StatusCode;
record.Online = true;
}
catch (WebException we)
{
try
{
record.HttpStatusCode = (int)((HttpWebResponse)we.Response).StatusCode;
}
catch (Exception ex)
{
Console.WriteLine($"Url: {web.Url} - chyba: {ex.Message}");
Debug.WriteLine(ex.Message);
Debug.WriteLine(ex.StackTrace);
}
record.Online = false;
}
return record;
}
}
}
Adam Gajdečka:4.11.2018 10:36
Díky. Vytvořil jsem tedy Konzolovou aplikaci v Core a kód mám tedy:
using RestSharp;
using MyApp.Areas.Websites.Models;
using MyApp.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace CheckWebsitesConsole
{
class Program
{
static void Main(string[] args)
{
using (var c = new Checker())
{
c.CheckWebsitesAsync();
}
}
}
public class Checker : IDisposable
{
private ApplicationDbContext _dbContext;
public Checker()
{
_dbContext = new ApplicationDbContext();
}
public void Dispose()
{
_dbContext.Dispose();
}
public async Task<bool> CheckWebsitesAsync()
{
List<Task<WebsiteStateRecord>> tasks = new List<Task<WebsiteStateRecord>>();
foreach (var web in _dbContext.Websites.ToList())
{
tasks.Add(Task.Run(() => CheckWebsiteAsync(web)));
}
var results = await Task.WhenAll(tasks);
_dbContext.WebsiteStateRecords.AddRange(results);
_dbContext.SaveChanges();
return true;
}
public async Task<WebsiteStateRecord> CheckWebsiteAsync(Website web)
{
WebsiteStateRecord record = new WebsiteStateRecord();
record.WebsiteId = web.Id;
record.Date = DateTime.Now;
List<HttpStatusCode> statusCodes = new List<HttpStatusCode>();
statusCodes.Add(HttpStatusCode.Found);
statusCodes.Add(HttpStatusCode.Moved);
statusCodes.Add(HttpStatusCode.MovedPermanently);
statusCodes.Add(HttpStatusCode.Redirect);
statusCodes.Add(HttpStatusCode.PermanentRedirect);
statusCodes.Add(HttpStatusCode.TemporaryRedirect);
statusCodes.Add(HttpStatusCode.RedirectMethod);
statusCodes.Add(HttpStatusCode.RedirectKeepVerb);
try
{
var client = new RestClient(web.Url);
var request = new RestRequest(Method.HEAD);
// execute the request
IRestResponse response = client.Execute(request);
record.HttpStatusCode = (int)response.StatusCode;
if (statusCodes.Contains(response.StatusCode))
{
string url1 = response.Headers.SingleOrDefault(s => s.Name == "Location").Value.ToString();
bool run = true;
while (run)
{
var client1 = new RestClient(url1);
var request1 = new RestRequest(Method.HEAD);
// execute the request
IRestResponse response1 = client1.Execute(request);
record.HttpStatusCode = (int)response1.StatusCode;
if (statusCodes.Contains(response1.StatusCode))
{
url1 = (response1.Headers.SingleOrDefault(s => s.Name == "Location").Value.ToString());
}
else if (response1.StatusCode == HttpStatusCode.OK)
{
record.Online = true;
break;
}
else
{
record.Online = false;
break;
}
}
}
else if (response.StatusCode == HttpStatusCode.OK)
{
record.Online = true;
}
else
{
record.Online = false;
}
}
catch (Exception)
{
}
return record;
}
}
}
A teď si tedy vytvořím v plánovači úlohu. která se bude spouštět: dotnet CheckWebsitesConsole.dll ? Je to tak? Díky
don.jarducius:4.11.2018 11:29
Ano, je to tak, při vytváření úlohy v plánovači ti wizzard nabídne základ, další funkce ti nabídne až při editaci úlohy. Nastav si potom kompatibilitu úlohy na ws2016, spustit nezávislé na přihlášení uživatele. Aktivační událostí si potom nastav 5 po jedné minutě s opakováním po 5 minutách po dobu 1 dne
Adam Gajdečka:4.11.2018 14:22
Nemůžu to zkompilovat, bohužel. Chyba:
Severity Code Description Project File Line Suppression State
Error CS7036 There is no argument given that corresponds to the required formal
parameter 'options' of
'ApplicationDbContext.ApplicationDbContext(DbContextOptions<ApplicationDbContext>)'
CheckWebsitesConsole
F:\Projekty\MyApp\Solution\MyApp\CheckWebsitesConsole\Program.cs 29
Active
public Checker()
{
_dbContext = new ApplicationDbContext(); //tady je chyba
}
:
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
//website
public DbSet<Website> Websites { get; set; }
public DbSet<WebsiteStateRecord> WebsiteStateRecords { get; set; }
}
don.jarducius:4.11.2018 19:35
Níže je funkční a otestovaná .net core konzolovka, problém při kompilaci je Dědění jakéhosi IdentityDbContextu namísto DbContextu z Microsoft.EntityFrameworkCore.
Nevím jaké další sloupce a tabulky máš v db, napsal jsem minium. Minimálně si přepis connectionString
DbContext:
using System;
using Microsoft.EntityFrameworkCore;
namespace ConsoleApp4
{
public class WebSiteDbContext : DbContext
{
public WebSiteDbContext() : base()
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("server=(local);uid=WebSiteChecker;pwd=TajneHeslo;Initial Catalog=WebSiteChecker");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<WebsiteStateRecord>().ToTable("WebsiteStateRecord").HasKey(p =>p.rowid);
modelBuilder.Entity<WebsiteStateRecord>().Property(p => p.rowid).ValueGeneratedOnAdd();
modelBuilder.Entity<Website>().ToTable("Website").HasKey(p => p.Id);
modelBuilder.Entity<Website>().Property(p => p.Id).ValueGeneratedOnAdd();
}
public DbSet<WebsiteStateRecord> WebsiteStateRecords { get; set; }
public DbSet<Website> Websites { get; set; }
}
public class WebsiteStateRecord
{
public int rowid { get; set; }
public int WebsiteId { get; set; }
public DateTime Date { get; set; }
public int HttpStatusCode { get; set; }
public bool Online { get; set; }
}
public class Website
{
public int Id { get; set; }
public string Url { get; set; }
}
}
program.cs
using System;
using System.Diagnostics;
using System.Linq;
using System.Net;
namespace ConsoleApp4
{
class Program
{
static void Main(string[] args)
{
using (var c = new Checker())
{
c.CheckWebsites();
}
}
}
public class Checker : IDisposable
{
private WebSiteDbContext _dbContext;
public Checker()
{
_dbContext = new WebSiteDbContext();
}
public void Dispose() {
_dbContext.Dispose();
}
public void CheckWebsites()
{
var weby = _dbContext.Websites.ToList();
var results = weby.AsParallel().Select(x => CheckWebsiteAsync(x)).ToList();
_dbContext.WebsiteStateRecords.AddRange(results);
_dbContext.SaveChanges();
}
public WebsiteStateRecord CheckWebsiteAsync(Website web)
{
WebsiteStateRecord record = new WebsiteStateRecord
{
WebsiteId = web.Id,
Date = DateTime.Now
};
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(web.Url);
request.AllowAutoRedirect = true;
request.Timeout = 30000;
request.Method = "HEAD";
try
{
HttpWebResponse httpRes = (HttpWebResponse)request.GetResponse();
record.HttpStatusCode = (int)httpRes.StatusCode;
record.Online = true;
}
catch (WebException we)
{
try
{
record.HttpStatusCode = (int)((HttpWebResponse)we.Response).StatusCode;
}
catch (Exception ex)
{
Console.WriteLine($"Url: {web.Url} - chyba: {ex.Message}");
Debug.WriteLine(ex.Message);
Debug.WriteLine(ex.StackTrace);
}
record.Online = false;
}
return record;
}
}
}
+20 Zkušeností
+2,50 Kč

don.jarducius:4.11.2018 19:38
Z nugetu si doimportuj:
Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.SqlServer
Adam Gajdečka:4.11.2018 20:19
Fantastické! Fakt to funguje! Já vůbec netušil, že se to takto dá
dělat
Když potřebuji více "Jobů" spouštět v rámci jednoho projektu, v různé časy, tak si mám takových konzolových aplikací tedy vytvořit několik, je to tak?
A díky za pomoc, naučil jsem se něco nového konečně
Tak záleží na tom, jestli dělají něco jiného, pokud dělají to samé,
můžeš použít tuto třeba 50x když budeš chtít
Adam Gajdečka:6.11.2018 10:31
Na mém PC všechno funguje, ale když jsem to právě nahrál na server, vytvořil úlohu stejným způsobem, tak se dostanu na error 0x80008083 (zobrazuje se v kolonce Last Run Result v Task Scheduler).
Zkusil jsem nainstalovat .NET Core 2.1 Runtime, ale to nepomohlo. Oprávnění k souboru by také mělo být správně nastaveno.
Bohužel k tomuto erroru jsem nebyl schopen nic dohledat.
A v historii je Task Scheduler successfully completed task.
A následně Task Scheduler successfully finished.
Díky za pomoc
Adam Gajdečka:6.11.2018 10:46
už jsem na to přišel, nakopíroval jsem na server jenom to .dll, bylo potřeba tam nakopírovat vše
Zobrazeno 21 zpráv z 21.