private static async Task <WebsiteDataModel> DownloadWebsiteAsync(string websiteURL)
        {
            WebsiteDataModel output = new WebsiteDataModel();
            WebClient        client = new WebClient();

            output.WebsiteUrl  = websiteURL;
            output.WebsiteData = await client.DownloadStringTaskAsync(websiteURL);

            return(output);
        }
        private static WebsiteDataModel DownloadWebsite(string websiteURL)
        {
            WebsiteDataModel output = new WebsiteDataModel();
            WebClient        client = new WebClient();

            output.WebsiteUrl  = websiteURL;
            output.WebsiteData = client.DownloadString(websiteURL);

            return(output);
        }
        public static List <WebsiteDataModel> RunDownloadSync()
        {
            List <string>           websites = PrepData();
            List <WebsiteDataModel> output   = new List <WebsiteDataModel>();

            foreach (string site in websites)
            {
                WebsiteDataModel results = DownloadWebsite(site);
                output.Add(results);
            }

            return(output);
        }
        /*
         *  async methods need to have Task as a return type. It is not recommended to use void return type for asyn methods. You can use the void return type in
         *  asynchronous event handlers, which require a void return type. For methods other than event handlers that don't return a value, you should return a Task
         *  instead, because an async method that returns void can't be awaited.
         */

        public static async Task <List <WebsiteDataModel> > RunDownloadAsync(IProgress <ProgressReportModel> progress, CancellationToken cancellationToken)
        {
            List <string>           websites = PrepData();
            List <WebsiteDataModel> output   = new List <WebsiteDataModel>();
            ProgressReportModel     report   = new ProgressReportModel();

            foreach (string site in websites)
            {
                /*
                 *  When you check the execution time of both Normal Execution & Async Execution they are nearly have the same execution time. The reason why the
                 *  execute on the same time or the same speed is because of the await keyword below.
                 *
                 *  Here, what it's doing is calling a first website say download that but wait for it. So, it blocks this particular method from getting to the next
                 *  website and it blocks the execution for the next website. So let's solve this problem with the help of parallel execution.
                 */

                WebsiteDataModel results = await Task.Run(() => DownloadWebsite(site));

                output.Add(results);

                /*
                 *  The logical place to add the CancellationToken is after we download our first website. The CancellationToken is activated if we say go ahead and
                 *  cancel that Task we're going to throw and exception essentially and then exception is going to be the operation cancelled exception.
                 *
                 *  That allows us couple of benefits:
                 *  1. It will stop right away and go back to the caller but it's also going to allow us to do any kind of clean-up on the caller end that we need to
                 *     do before we continue. Say we have open connections or something like that we can go ahead and close them.
                 *  2. Another thing is we should do some cleanup in here if we need to before we actually throw this. IsCancellationRequested that can check to see
                 *     if the cancellation has been requested. We check the value of IsCancellationRequested in the if condition and do cleanup and then
                 *     ThrowIfCancellationRequested().
                 */

                cancellationToken.ThrowIfCancellationRequested();

                report.SitesDownloaded    = output;
                report.PercentageComplete = (output.Count * 100) / websites.Count;
                progress.Report(report);
            }
            return(output);
        }
        public async static Task <List <WebsiteDataModel> > RunDownloadParallelAsyncV2(IProgress <ProgressReportModel> progress)
        {
            List <string>           websites = PrepData();
            List <WebsiteDataModel> output   = new List <WebsiteDataModel>();
            ProgressReportModel     report   = new ProgressReportModel();

            /*
             *  For Parallel.ForEach we are passing a List of string and then for each item we are going to do an Action. What site represents is each of the website.
             *  This is same essentially for RunDownloadSync except for the fact that ForEach it does each of these download in parallel to each other. Parallel.ForEach
             *  gets a list of things do each one in parallel the others.
             *
             *  The difference between Parallel.ForEach and RunDownloadParallelAsync (where we created our own parallel execution). The difference is Parallel.ForEach
             *  do the task in Parallel in a synchronous way meaning it locks everything up until they're all done. So the longest download that's how long we have to
             *  wait for this to be done. It's a short amount of time then the code written in RunDownloadSync method, which actually downloads one at a time and the
             *  time is cumulative. There is benefit in using Parallel.ForEach but still it's synchronous.
             *
             *  RunDownloadParallelAsyncV2 is actually better and faster than RunDownloadParallelAsync because it will return the smallest website name which is
             *  downloaded first and returns the largest website which is downloaded last. Unlike in RunDownloadParallelAsync website is downloaded in an order they
             *  were added to the list.
             */

            await Task.Run(() =>
            {
                Parallel.ForEach <string>(websites, (site) =>
                {
                    WebsiteDataModel results = DownloadWebsite(site);
                    output.Add(results);

                    report.SitesDownloaded    = output;
                    report.PercentageComplete = (output.Count * 100) / websites.Count;
                    progress.Report(report);
                });
            });

            return(output);
        }
        public static List <WebsiteDataModel> RunDownloadParallelSync()
        {
            List <string>           websites = PrepData();
            List <WebsiteDataModel> output   = new List <WebsiteDataModel>();

            /*
             *  For Parallel.ForEach we are passing a List of string and then for each item we are going to do an Action. What site represents is each of the website.
             *  This is same essentially for RunDownloadSync except for the fact that ForEach it does each of these download in parallel to each other. Parallel.ForEach
             *  gets a list of things do each one in parallel the others.
             *
             *  The difference between Parallel.ForEach and RunDownloadParallelAsync (where we created our own parallel execution). The difference is Parallel.ForEach
             *  do the task in Parallel in a synchronous way meaning it locks everything up until they're all done. So the longest download that's how long we have to
             *  wait for this to be done. It's a short amount of time then the code written in RunDownloadSync method, which actually downloads one at a time and the
             *  time is cumulative. There is benefit in using Parallel.ForEach but still it's synchronous.
             */

            Parallel.ForEach <string>(websites, (site) =>
            {
                WebsiteDataModel results = DownloadWebsite(site);
                output.Add(results);
            });

            return(output);
        }