4

Entity Framework 5 を使用してデータベースのキューから電子メールを読み取り、System.Net.Mail.SmtpClient を使用して SMTP サーバーに送信する Windows サービスがあります。

F# アプリケーションを作成するのは初めての試みです。私は C# のバックグラウンドを持っています。より機能的にしたり、F# の機能を十分に活用したりするには、どのように改善すればよいでしょうか? これらの変更を行う利点について説明していただけますか?

ワーカーはサービス ホストによって構築され、サービスの開始時にその作業関数が呼び出され、サービスの停止時に ContinueWorking が false に設定されます。

namespace EmailService

open log4net

open System
open System.Linq
open System.Net.Mail

open EmailService.Context

type Worker(contextFactory: EmailContextFactory, mailClient: ISmtpClient, logger: ILog) =

    let MapToMessage(email : Email) =
        let message = new MailMessage()
        message.Sender <- new MailAddress(email.From)
        message.From <- new MailAddress(email.From)
        message.Subject <- email.Subject
        message.Body <- email.Body
        message.IsBodyHtml <- email.IsBodyHtml
        message.To.Add(email.To)
        (email, message)

    member val ContinueWorking = true with get, set
    member this.Work() = 
        logger.Info "Starting work"

        let mutable unsentEmails = Array.empty<Email>
        while this.ContinueWorking do

            use context = contextFactory.GetEntities()
            while this.ContinueWorking && Array.isEmpty unsentEmails do
                System.Threading.Thread.Sleep(1000)
                unsentEmails <- query { for q in context.QueueItems do
                                           where (q.Error = null)
                                           select q.Email }
                                |> query.Take(10)
                                |> query.toArray

            Array.map MapToMessage unsentEmails
                |> Array.iter (fun (email, message) -> 
                    try
                        mailClient.SendMail(message)
                        email.DateSent <- new Nullable<DateTime>(DateTime.UtcNow)
                        context.QueueItems.Remove(email.QueueItem) |> ignore
                    with
                        | ex -> 
                            logger.Error(ex)
                            email.QueueItem.Error <- ex.ToString())

            context.SaveChanges() |> ignore
            logger.Info (sprintf "Sent %d emails" unsentEmails.Length)



        logger.Info "Work complete"
4

1 に答える 1

5

このWorkメソッドは、最終的に停止する必要がある実行時間の長いプロセスを作成します。F# でこれを表すより良い方法は、非同期ワークフローを使用することです。非同期ワークフローは、スレッドをブロックせずに中断でき (したがって は必要ありませんThread.Sleep)、 を使用して簡単にキャンセルできますCancellationToken

そうでなければ、あなたのコードは私には良さそうです。いくつかのマイナーな変更を行いました ( takeF# クエリ構文での使用など、これはちょっとした便利な機能です)。

whileコードに 2 つのネストされたループがある理由もよくわかりません。それは必要ですか?いいえの場合は、次のように書くことができると思います。

member this.Work() = async {
    logger.Info "Starting work"
    while true do
        do! Async.Sleep(1000)
        use context = contextFactory.GetEntities()
        let unsentEmails =
          query { for q in context.QueueItems do
                  where (q.Error = null)
                  select q.Email 
                  take 10 }
        unsentEmails
        |> Array.map MapToMessage 
        |> Array.iter (fun (email, message) -> 
                try
                    mailClient.SendMail(message)
                    email.DateSent <- new Nullable<DateTime>(DateTime.UtcNow)
                    context.QueueItems.Remove(email.QueueItem) |> ignore
                with ex ->
                     logger.Error(ex)
                     email.QueueItem.Error <- ex.ToString())
        context.SaveChanges() |> ignore
        logger.Info (sprintf "Sent %d emails" unsentEmails.Length)
    logger.Info "Work complete" }

プロセスを開始する (そして後で停止する) には、次のように記述します。

// Start the work
let cts = new CancellationTokenSource()
Async.Start(worker.Work(), cts.Token)

// Stop the worker
cts.Cancel()
于 2012-11-22T12:54:53.643 に答える