/// <summary>
        /// Run the sample async.
        /// </summary>
        /// <param name="config">The param is of type ConfigWrapper. This class reads values from local configuration file.</param>
        /// <returns></returns>
        private static async Task RunAsync(ConfigWrapper config)
        {
            IAzureMediaServicesClient client;

            try
            {
                client = await CreateMediaServicesClientAsync(config);
            }
            catch (Exception e)
            {
                Console.Error.WriteLine("TIP: Make sure that you have filled out the appsettings.json file before running this sample.");
                Console.Error.WriteLine($"{e.Message}");
                return;
            }

            // Set the polling interval for long running operations to 2 seconds.
            // The default value is 30 seconds for the .NET client SDK
            client.LongRunningOperationRetryTimeout = 2;

            // Creating a unique suffix so that we don't have name collisions if you run the sample
            // multiple times without cleaning up.
            string uniqueness      = Guid.NewGuid().ToString("N");
            string jobName         = $"job-{uniqueness}";
            string outputAssetName = $"output-{uniqueness}";
            string inputAssetName  = $"input-{uniqueness}";

            // Create a video analyzer preset with video insights.
            Preset preset = new VideoAnalyzerPreset(null, InsightsType.VideoInsightsOnly);

            // Ensure that you have the desired encoding Transform. This is really a one time setup operation.
            // Once it is created, we won't delete it.
            Transform videoAnalyzerTransform = await GetOrCreateTransformAsync(client, config.ResourceGroup, config.AccountName, VideoAnalyzerTransformName, preset);

            // Create a new input Asset and upload the specified local video file into it.
            await CreateInputAssetAsync(client, config.ResourceGroup, config.AccountName, inputAssetName, InputMP4FileName);

            // Output from the encoding Job must be written to an Asset, so let's create one
            Asset outputAsset = await CreateOutputAssetAsync(client, config.ResourceGroup, config.AccountName, outputAssetName);

            Job job = await SubmitJobAsync(client, config.ResourceGroup, config.AccountName, VideoAnalyzerTransformName, jobName, inputAssetName, outputAsset.Name);

            EventProcessorHost eventProcessorHost = null;

            try
            {
                // First we will try to process Job events through Event Hub in real-time. If this fails for any reason,
                // we will fall-back on polling Job status instead.

                // Please refer README for Event Hub and storage settings.
                string storageConnectionString = string.Format("DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1}",
                                                               config.StorageAccountName, config.StorageAccountKey);

                // Create a new host to process events from an Event Hub.
                Console.WriteLine("Creating a new host to process events from an Event Hub...");
                eventProcessorHost = new EventProcessorHost(config.EventHubName,
                                                            PartitionReceiver.DefaultConsumerGroupName, config.EventHubConnectionString,
                                                            storageConnectionString, config.StorageContainerName);

                // Create an AutoResetEvent to wait for the job to finish and pass it to EventProcessor so that it can be set when a final state event is received.
                AutoResetEvent jobWaitingEvent = new AutoResetEvent(false);

                // Registers the Event Processor Host and starts receiving messages. Pass in jobWaitingEvent so it can be called back.
                await eventProcessorHost.RegisterEventProcessorFactoryAsync(new MediaServicesEventProcessorFactory(jobName, jobWaitingEvent),
                                                                            EventProcessorOptions.DefaultOptions);

                // Create a Task list, adding a job waiting task and a timer task. Other tasks can be added too.
                IList <Task> tasks = new List <Task>();

                // Add a task to wait for the job to finish. The AutoResetEvent will be set when a final state is received by EventProcessor.
                Task jobTask = Task.Run(() =>
                                        jobWaitingEvent.WaitOne());
                tasks.Add(jobTask);

                // 30 minutes timeout.
                var tokenSource = new CancellationTokenSource();
                var timeout     = Task.Delay(30 * 60 * 1000, tokenSource.Token);
                tasks.Add(timeout);

                // Wait for tasks.
                if (await Task.WhenAny(tasks) == jobTask)
                {
                    // Job finished. Cancel the timer.
                    tokenSource.Cancel();
                    // Get the latest status of the job.
                    job = await client.Jobs.GetAsync(config.ResourceGroup, config.AccountName, VideoAnalyzerTransformName, jobName);
                }
                else
                {
                    // Timeout happened, Something might be wrong with job events. Fall-back on polling instead.
                    jobWaitingEvent.Set();
                    throw new Exception("Timeout happened.");
                }
            }
            catch (Exception)
            {
                Console.WriteLine("Warning: Failed to connect to Event Hub, please refer README for Event Hub and storage settings.");

                // Polling is not a recommended best practice for production applications because of the latency it introduces.
                // Overuse of this API may trigger throttling. Developers should instead use Event Grid.
                Console.WriteLine("Polling job status...");
                job = await WaitForJobToFinishAsync(client, config.ResourceGroup, config.AccountName, VideoAnalyzerTransformName, jobName);
            }
            finally
            {
                if (eventProcessorHost != null)
                {
                    Console.WriteLine("Job final state received, unregistering event processor...");

                    // Disposes of the Event Processor Host.
                    await eventProcessorHost.UnregisterEventProcessorAsync();

                    Console.WriteLine();
                }
            }

            if (job.State == JobState.Finished)
            {
                Console.WriteLine("Job finished.");
                if (!Directory.Exists(OutputFolderName))
                {
                    Directory.CreateDirectory(OutputFolderName);
                }

                await DownloadOutputAssetAsync(client, config.ResourceGroup, config.AccountName, outputAsset.Name, OutputFolderName);
            }

            await CleanUpAsync(client, config.ResourceGroup, config.AccountName, VideoAnalyzerTransformName, inputAssetName, outputAssetName, jobName);
        }
        /// <summary>
        /// Creates the AzureMediaServicesClient object based on the credentials
        /// supplied in local configuration file.
        /// </summary>
        /// <param name="config">The param is of type ConfigWrapper. This class reads values from local configuration file.</param>
        /// <returns></returns>
        private static async Task <IAzureMediaServicesClient> CreateMediaServicesClientAsync(ConfigWrapper config)
        {
            var credentials = await GetCredentialsAsync(config);

            return(new AzureMediaServicesClient(config.ArmEndpoint, credentials)
            {
                SubscriptionId = config.SubscriptionId,
            });
        }